diff --git a/.github/ISSUE_TEMPLATE/1-p5.js-2.0-bug-report.yml b/.github/ISSUE_TEMPLATE/1-p5.js-2.0-bug-report.yml index 6387aaefe3..79dc070cf7 100644 --- a/.github/ISSUE_TEMPLATE/1-p5.js-2.0-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/1-p5.js-2.0-bug-report.yml @@ -1,6 +1,6 @@ name: 🐛 p5.js 2.0+ Bug Report description: This template is for reporting bugs (broken or incorrect behaviour). If you have questions about your own code, please visit our forum discourse.processing.org instead. -title: "[p5.js 2.0+ Bug Report]: " +title: '[p5.js 2.0+ Bug Report]: ' labels: [p5.js 2.0+] body: - type: markdown @@ -16,25 +16,25 @@ body: label: Most appropriate sub-area of p5.js? description: You may select more than one. options: - - label: Accessibility - - label: Color - - label: Core/Environment/Rendering - - label: Data - - label: DOM - - label: Events - - label: Image - - label: IO - - label: Math - - label: Typography - - label: Utilities - - label: WebGL - - label: WebGPU - - label: p5.strands - - label: Build process - - label: Unit testing - - label: Internationalization - - label: Friendly errors - - label: Other (specify if possible) + - label: Accessibility + - label: Color + - label: Core/Environment/Rendering + - label: Data + - label: DOM + - label: Events + - label: Image + - label: IO + - label: Math + - label: Typography + - label: Utilities + - label: WebGL + - label: WebGPU + - label: p5.strands + - label: Build process + - label: Unit testing + - label: Internationalization + - label: Friendly errors + - label: Other (specify if possible) - type: input attributes: label: p5.js version @@ -50,14 +50,14 @@ body: - type: input attributes: label: Operating system - description: "Ex: Windows/MacOSX/Linux/Android/iOS along with version" + description: 'Ex: Windows/MacOSX/Linux/Android/iOS along with version' validations: required: false - type: textarea attributes: label: Steps to reproduce this description: Include a simple code snippet that demonstrates the problem, along with any console errors produced. If this isn't possible, then simply describe the issue as best you can! - value: "### Steps: + value: '### Steps: 1. @@ -72,9 +72,9 @@ body: ```js - // Paste your code here :) + // Paste your code here :) - ```" + ```' validations: required: true diff --git a/.github/ISSUE_TEMPLATE/2-found-a-bug.yml b/.github/ISSUE_TEMPLATE/2-found-a-bug.yml index 276d51d924..9e04895b7f 100644 --- a/.github/ISSUE_TEMPLATE/2-found-a-bug.yml +++ b/.github/ISSUE_TEMPLATE/2-found-a-bug.yml @@ -1,14 +1,14 @@ name: 📃 p5.js 1.x Bug Report description: This template is for reporting bugs (broken or incorrect behaviour) on p5.js versions 1.11 or earlier. Please note there is no active development on p5.js version 1.x. -title: "[p5.js 1.x Bug Report]: " +title: '[p5.js 1.x Bug Report]: ' labels: [Bug] body: -- type: checkboxes - id: sub-area - attributes: - label: Most appropriate sub-area of p5.js? - description: You may select more than one. - options: + - type: checkboxes + id: sub-area + attributes: + label: Most appropriate sub-area of p5.js? + description: You may select more than one. + options: - label: Accessibility - label: Color - label: Core/Environment/Rendering @@ -28,46 +28,47 @@ body: - label: Internationalization - label: Friendly errors - label: Other (specify if possible) -- type: input - attributes: - label: p5.js version - description: You can find this in the first line of the p5.js file. - validations: - required: false -- type: input - attributes: - label: Web browser and version - description: In the address bar, on Chrome enter "chrome://version", on Firefox enter "about:support". On Safari, use "About Safari". - validations: - required: false -- type: input - attributes: - label: Operating system - description: "Ex: Windows/MacOSX/Linux/Android/iOS along with version" - validations: - required: false -- type: textarea - attributes: - label: Steps to reproduce this - description: Include a simple code snippet that demonstrates the problem, along with any console errors produced. If this isn't possible, then simply describe the issue as best you can! - value: "### Steps: - - 1. + - type: input + attributes: + label: p5.js version + description: You can find this in the first line of the p5.js file. + validations: + required: false + - type: input + attributes: + label: Web browser and version + description: In the address bar, on Chrome enter "chrome://version", on Firefox enter "about:support". On Safari, use "About Safari". + validations: + required: false + - type: input + attributes: + label: Operating system + description: 'Ex: Windows/MacOSX/Linux/Android/iOS along with version' + validations: + required: false + - type: textarea + attributes: + label: Steps to reproduce this + description: Include a simple code snippet that demonstrates the problem, along with any console errors produced. If this isn't possible, then simply describe the issue as best you can! + value: | + ### Steps: - 2. + 1. - 3. + 2. + 3. - ### Snippet: - - ```js + ### Snippet: - // Paste your code here :) + ```js - ```" - validations: - required: true + // Paste your code here :) + + + ```" + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/5-discussion.yml b/.github/ISSUE_TEMPLATE/5-discussion.yml index 115fb834cb..a34525bbcd 100644 --- a/.github/ISSUE_TEMPLATE/5-discussion.yml +++ b/.github/ISSUE_TEMPLATE/5-discussion.yml @@ -2,9 +2,9 @@ name: 💭 Discussion description: This template is for starting a discussion. labels: [Discussion] body: -- type: textarea - attributes: - label: Topic - description: What do you want to discuss with the group? - validations: - required: true + - type: textarea + attributes: + label: Topic + description: What do you want to discuss with the group? + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index bf41b2b1da..1cf5715a21 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -12,4 +12,3 @@ contact_links: - name: 💬 Forum url: https://discourse.processing.org/c/p5js about: Have other questions about using p5.js? Ask them here! - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 45b15440c0..9ccfc8b9ed 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,16 +3,17 @@ In the description field of this PR, include "resolves #XXXX" tagging the issue you are fixing. If this PR addresses the issue but doesn't completely resolve it (ie the issue should remain open after your PR is merged), write "addresses #XXXX".--> + Resolves #[Add issue number here] - Changes: +Changes: - - Screenshots of the change: +Screenshots of the change: #### PR Checklist + diff --git a/.github/config.yml b/.github/config.yml index 7a92f38473..5e413a75c9 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -4,26 +4,26 @@ # Comment to be posted to on first time issues newIssueWelcomeComment: > - Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, please make sure to fill out the inputs in the issue forms. - - For guidance on contributing, check out our [contributing guidelines](https://github.com/processing/p5.js/blob/main/CONTRIBUTING.md) and other [resources for contributors](https://p5js.org/contribute/). + Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, please make sure to fill out the inputs in the issue forms. + + For guidance on contributing, check out our [contributing guidelines](https://github.com/processing/p5.js/blob/main/CONTRIBUTING.md) and other [resources for contributors](https://p5js.org/contribute/). - 💬 If you have questions or need support, feel free to join the [Processing Foundation Forum](https://discourse.processing.org/) or visit our [Community page](https://p5js.org/community/). + 💬 If you have questions or need support, feel free to join the [Processing Foundation Forum](https://discourse.processing.org/) or visit our [Community page](https://p5js.org/community/). - 📜 Please also review our [Code of Conduct](https://p5js.org/code-of-conduct/) to understand our community standards. + 📜 Please also review our [Code of Conduct](https://p5js.org/code-of-conduct/) to understand our community standards. - Thank You! + Thank You! # Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome # Comment to be posted to on PRs from first time contributors in your repository newPRWelcomeComment: > - 🎉 Thanks for opening this pull request! For guidance on contributing, check out our [contributor guidelines](https://p5js.org/contribute/contributor_guidelines/) and other [resources for contributors](https://p5js.org/contribute)! + 🎉 Thanks for opening this pull request! For guidance on contributing, check out our [contributor guidelines](https://p5js.org/contribute/contributor_guidelines/) and other [resources for contributors](https://p5js.org/contribute)! - 🤔 Please ensure that your PR links to an issue, which has been approved for work by a maintainer; otherwise, there might already be someone working on it, or still ongoing discussion about implementation. You are welcome to join the discussion in an Issue if you're not sure! + 🤔 Please ensure that your PR links to an issue, which has been approved for work by a maintainer; otherwise, there might already be someone working on it, or still ongoing discussion about implementation. You are welcome to join the discussion in an Issue if you're not sure! - 🌸 Once your PR is merged, be sure to [add yourself](https://github.com/processing/p5.js/issues/2309) to the [list of contributors on the readme page](https://github.com/processing/p5.js#contributors) ! + 🌸 Once your PR is merged, be sure to [add yourself](https://github.com/processing/p5.js/issues/2309) to the [list of contributors on the readme page](https://github.com/processing/p5.js#contributors) ! - Thank You! + Thank You! # Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge diff --git a/.github/first-timers.yml b/.github/first-timers.yml index d6c563c778..1067540d99 100644 --- a/.github/first-timers.yml +++ b/.github/first-timers.yml @@ -11,4 +11,4 @@ labels: - hacktoberfest # If you would like to add your own template for the issue, add an .md file to your .github folder -# template: .github/first-timers-issue-template.md \ No newline at end of file +# template: .github/first-timers-issue-template.md diff --git a/.github/labeler.yml b/.github/labeler.yml index 058d2686a0..c30c43b2b0 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,39 +1,39 @@ -"Area:Accessibility": +'Area:Accessibility': - '\[[xX]\]\s*Accessibility' -"Area:Color": +'Area:Color': - '\[[xX]\]\s*Color' -"Area:Core": +'Area:Core': - '\[[xX]\]\s*Core' -"Area:Data": +'Area:Data': - '\[[xX]\]\s*Data' -"Area:DOM": +'Area:DOM': - '\[[xX]\]\s*DOM' -"Area:Events": +'Area:Events': - '\[[xX]\]\s*Events' -"Area:Image": +'Area:Image': - '\[[xX]\]\s*Image' -"Area:IO": +'Area:IO': - '\[[xX]\]\s*IO' -"Area:Math": +'Area:Math': - '\[[xX]\]\s*Math' -"Area:Typography": +'Area:Typography': - '\[[xX]\]\s*Typography' -"Area:Utilities": +'Area:Utilities': - '\[[xX]\]\s*Utilities' -"Area:WebGL": +'Area:WebGL': - '\[[xX]\]\s*WebGL' -"Internationalization": +'Internationalization': - '\[[xX]\]\s*Internationalization' -"DevOps": +'DevOps': - '\[[xX]\]\s*Build Process' - '\[[xX]\]\s*Unit Testing' -"Build Process": +'Build Process': - '\[[xX]\]\s*Build Process' -"Unit Testing": +'Unit Testing': - '\[[xX]\]\s*Unit Testing' -"Friendly Errors": +'Friendly Errors': - '\[[xX]\]\s*Friendly Errors' -"p5.strands": +'p5.strands': - '\[[xX]\]\s*p5.strands' -"Area:WebGPU": +'Area:WebGPU': - '\[[xX]\]\s*WebGPU' diff --git a/.github/release.yml b/.github/release.yml index 566b88c27e..23af81d40f 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -16,4 +16,4 @@ changelog: - dependabot - title: New Contributors 💗 authors: - - allcontributors \ No newline at end of file + - allcontributors diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 7f23015ba2..8216351c55 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -28,12 +28,12 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false - + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 22.x - + - name: Verify Chrome (Ubuntu) if: matrix.os == 'ubuntu-latest' && matrix.browser == 'chrome' run: | @@ -43,7 +43,7 @@ jobs: if: matrix.os == 'windows-latest' && matrix.browser == 'chrome' run: | & "C:\Program Files\Google\Chrome\Application\chrome.exe" --version - + - name: Get node modules run: npm ci env: @@ -79,4 +79,4 @@ jobs: CI: true - name: fail job if tests failed if: steps.test.outcome != 'success' - run: exit 1 \ No newline at end of file + run: exit 1 diff --git a/.github/workflows/contributors-png.yml b/.github/workflows/contributors-png.yml index 321b5ddfa5..488039a9d3 100644 --- a/.github/workflows/contributors-png.yml +++ b/.github/workflows/contributors-png.yml @@ -37,9 +37,9 @@ jobs: - name: Create Pull Request uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 with: - commit-message: "Update contributors.png from .all-contributorsrc" + commit-message: 'Update contributors.png from .all-contributorsrc' branch: update-contributors-png - title: "chore: update contributors.png from .all-contributorsrc" - body: "This PR updates the contributors.png to reflect changes in .all-contributorsrc" + title: 'chore: update contributors.png from .all-contributorsrc' + body: 'This PR updates the contributors.png to reflect changes in .all-contributorsrc' add-paths: contributors.png token: ${{ secrets.ACCESS_TOKEN }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index cc10da56c3..f5f4ef1cd7 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,4 +1,4 @@ -name: "Issue Labeler" +name: 'Issue Labeler' on: issues: types: [opened, edited] @@ -11,6 +11,6 @@ jobs: steps: - uses: github/issue-labeler@98b5412841f6c4b0b3d9c29d53c13fad16bd7de2 # v3.2 with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" + repo-token: '${{ secrets.GITHUB_TOKEN }}' configuration-path: .github/labeler.yml enable-versioned-regex: 0 diff --git a/.github/workflows/release-workflow-v1.yml b/.github/workflows/release-workflow-v1.yml index f84547f3ad..df409e2b80 100644 --- a/.github/workflows/release-workflow-v1.yml +++ b/.github/workflows/release-workflow-v1.yml @@ -80,7 +80,7 @@ jobs: uses: JS-DevTools/npm-publish@0f451a94170d1699fd50710966d48fb26194d939 # v1 with: token: ${{ secrets.NPM_TOKEN }} - + # 4. Update p5.js website - name: Clone p5.js website if: ${{ steps.semver.outputs.is-prerelease != 'true' }} @@ -116,7 +116,7 @@ jobs: github_token: ${{ secrets.ACCESS_TOKEN }} branch: v1 directory: website/ - repository: processing/p5.js-website + repository: processing/p5.js-website # 5. Update Bower files - name: Checkout Bower repo diff --git a/.github/workflows/release-workflow-v2.yml b/.github/workflows/release-workflow-v2.yml index 0ee420c595..939d378884 100644 --- a/.github/workflows/release-workflow-v2.yml +++ b/.github/workflows/release-workflow-v2.yml @@ -86,7 +86,7 @@ jobs: with: token: ${{ secrets.NPM_TOKEN }} tag: ${{ steps.semver.outputs.is-prerelease != 'true' && 'latest' || 'beta' }} - + # 4. Update p5.js website - name: Clone p5.js website if: ${{ steps.semver.outputs.is-prerelease != 'true' }} diff --git a/.github/workflows/stewards-update.yml b/.github/workflows/stewards-update.yml index 4590619062..5f99baf3da 100644 --- a/.github/workflows/stewards-update.yml +++ b/.github/workflows/stewards-update.yml @@ -40,8 +40,8 @@ jobs: - name: Create Pull Request uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 with: - commit-message: "Update README table from stewards.yml" + commit-message: 'Update README table from stewards.yml' branch: update-readme-table - title: "chore: update README table from stewards.yml" - body: "This PR updates the README.md table to reflect changes in stewards.yml." + title: 'chore: update README table from stewards.yml' + body: 'This PR updates the README.md table to reflect changes in stewards.yml.' add-paths: README.md diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000000..267f27c508 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,18 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "ignorePatterns": [ + "test/manual-test-examples/*", + "lib/*", + ".all-contributorsrc", + "test/mockServiceWorker.js", + "contributor_docs/project_wrapups/*", + "*.json" + ], + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma":"none", + "arrowParens": "avoid" +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000000..3ce7393ab2 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,1402 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": [], + "categories": { + "correctness": "off" + }, + "env": { + "builtin": true + }, + "ignorePatterns": [ + "!**/*.gitkeep", + ".nyc_output/*", + "**/local.code-workspace", + "node_modules/*", + "**/analyzer/", + "**/bower-repo/", + "**/coverage/", + "examples/3d/", + "experiments/*", + "**/dist/", + "docs/reference/*", + "docs/data.json", + "lib_old/*", + "lib/p5.*", + "lib/p5-test.js", + "lib/modules", + "**/p5-website/", + "**/preview/", + "**/release/", + "**/__screenshots__/", + "**/*.d.ts", + "**/p5.zip", + "**/yarn.lock", + "**/actual-screenshots/", + "**/visual-report.html", + "**/todo.md", + "**/*.DS_Store", + "**/.idea", + "**/.project", + "node_modules/**", + ".github/**", + ".vscode/**", + "contributor_docs/archive/**", + "contributor_docs/images/**", + "dist/**", + "lib/**", + "src/core/reference.js", + "src/type/lib/Typr.js", + "test/**/*", + "!test/unit/", + "!test/unit/**/*", + "translations/**", + "types/**", + "utils/sample-linter.mjs" + ], + "overrides": [ + { + "files": [ + "**/*.js", + "**/*.mjs" + ], + "rules": { + "constructor-super": "warn", + "for-direction": "warn", + "getter-return": "warn", + "no-async-promise-executor": "off", + "no-case-declarations": "warn", + "no-class-assign": "warn", + "no-compare-neg-zero": "warn", + "no-cond-assign": [ + "warn", + "except-parens" + ], + "no-const-assign": "warn", + "no-constant-binary-expression": "warn", + "no-constant-condition": "warn", + "no-control-regex": "warn", + "no-debugger": "warn", + "no-delete-var": "warn", + "no-dupe-class-members": "warn", + "no-dupe-else-if": "warn", + "no-dupe-keys": "warn", + "no-duplicate-case": "warn", + "no-empty": [ + "warn", + { + "allowEmptyCatch": true + } + ], + "no-empty-character-class": "warn", + "no-empty-pattern": "warn", + "no-empty-static-block": "warn", + "no-ex-assign": "warn", + "no-extra-boolean-cast": "warn", + "no-fallthrough": "warn", + "no-func-assign": "warn", + "no-global-assign": "warn", + "no-import-assign": "warn", + "no-invalid-regexp": "warn", + "no-irregular-whitespace": "warn", + "no-loss-of-precision": "warn", + "no-misleading-character-class": "warn", + "no-new-native-nonconstructor": "warn", + "no-nonoctal-decimal-escape": "warn", + "no-obj-calls": "warn", + "no-prototype-builtins": "off", + "no-redeclare": "warn", + "no-regex-spaces": "warn", + "no-self-assign": "warn", + "no-setter-return": "warn", + "no-shadow-restricted-names": "warn", + "no-sparse-arrays": "warn", + "no-this-before-super": "warn", + "no-unexpected-multiline": "warn", + "no-unreachable": "warn", + "no-unsafe-finally": "warn", + "no-unsafe-negation": "warn", + "no-unsafe-optional-chaining": "warn", + "no-unused-labels": "warn", + "no-unused-private-class-members": "warn", + "no-unused-vars": [ + "warn", + { + "args": "none" + } + ], + "no-useless-backreference": "warn", + "no-useless-catch": "warn", + "no-useless-escape": "warn", + "no-with": "warn", + "require-yield": "warn", + "use-isnan": "warn", + "valid-typeof": "warn", + "eqeqeq": [ + "warn", + "smart" + ], + "new-cap": "off", + "no-caller": "warn", + "no-console": "off", + "no-use-before-define": [ + "warn", + { + "functions": false + } + ] + } + }, + { + "files": [ + "src/**/*.js" + ], + "globals": { + "AbortController": "readonly", + "AbortSignal": "readonly", + "AbsoluteOrientationSensor": "readonly", + "AbstractRange": "readonly", + "Accelerometer": "readonly", + "addEventListener": "readonly", + "ai": "readonly", + "AI": "readonly", + "AICreateMonitor": "readonly", + "AITextSession": "readonly", + "alert": "readonly", + "AnalyserNode": "readonly", + "Animation": "readonly", + "AnimationEffect": "readonly", + "AnimationEvent": "readonly", + "AnimationPlaybackEvent": "readonly", + "AnimationTimeline": "readonly", + "AsyncDisposableStack": "readonly", + "atob": "readonly", + "Attr": "readonly", + "Audio": "readonly", + "AudioBuffer": "readonly", + "AudioBufferSourceNode": "readonly", + "AudioContext": "readonly", + "AudioData": "readonly", + "AudioDecoder": "readonly", + "AudioDestinationNode": "readonly", + "AudioEncoder": "readonly", + "AudioListener": "readonly", + "AudioNode": "readonly", + "AudioParam": "readonly", + "AudioParamMap": "readonly", + "AudioProcessingEvent": "readonly", + "AudioScheduledSourceNode": "readonly", + "AudioSinkInfo": "readonly", + "AudioWorklet": "readonly", + "AudioWorkletGlobalScope": "readonly", + "AudioWorkletNode": "readonly", + "AudioWorkletProcessor": "readonly", + "AuthenticatorAssertionResponse": "readonly", + "AuthenticatorAttestationResponse": "readonly", + "AuthenticatorResponse": "readonly", + "BackgroundFetchManager": "readonly", + "BackgroundFetchRecord": "readonly", + "BackgroundFetchRegistration": "readonly", + "BarcodeDetector": "readonly", + "BarProp": "readonly", + "BaseAudioContext": "readonly", + "BatteryManager": "readonly", + "BeforeUnloadEvent": "readonly", + "BiquadFilterNode": "readonly", + "Blob": "readonly", + "BlobEvent": "readonly", + "Bluetooth": "readonly", + "BluetoothCharacteristicProperties": "readonly", + "BluetoothDevice": "readonly", + "BluetoothRemoteGATTCharacteristic": "readonly", + "BluetoothRemoteGATTDescriptor": "readonly", + "BluetoothRemoteGATTServer": "readonly", + "BluetoothRemoteGATTService": "readonly", + "BluetoothUUID": "readonly", + "blur": "readonly", + "BroadcastChannel": "readonly", + "BrowserCaptureMediaStreamTrack": "readonly", + "btoa": "readonly", + "ByteLengthQueuingStrategy": "readonly", + "Cache": "readonly", + "caches": "readonly", + "CacheStorage": "readonly", + "cancelAnimationFrame": "readonly", + "cancelIdleCallback": "readonly", + "CanvasCaptureMediaStream": "readonly", + "CanvasCaptureMediaStreamTrack": "readonly", + "CanvasGradient": "readonly", + "CanvasPattern": "readonly", + "CanvasRenderingContext2D": "readonly", + "CaptureController": "readonly", + "CaretPosition": "readonly", + "CDATASection": "readonly", + "ChannelMergerNode": "readonly", + "ChannelSplitterNode": "readonly", + "ChapterInformation": "readonly", + "CharacterBoundsUpdateEvent": "readonly", + "CharacterData": "readonly", + "clearInterval": "readonly", + "clearTimeout": "readonly", + "clientInformation": "readonly", + "Clipboard": "readonly", + "ClipboardEvent": "readonly", + "ClipboardItem": "readonly", + "close": "readonly", + "closed": "readonly", + "CloseEvent": "readonly", + "CloseWatcher": "readonly", + "CommandEvent": "readonly", + "Comment": "readonly", + "CompositionEvent": "readonly", + "CompressionStream": "readonly", + "confirm": "readonly", + "console": "readonly", + "ConstantSourceNode": "readonly", + "ContentVisibilityAutoStateChangeEvent": "readonly", + "ConvolverNode": "readonly", + "CookieChangeEvent": "readonly", + "CookieDeprecationLabel": "readonly", + "cookieStore": "readonly", + "CookieStore": "readonly", + "CookieStoreManager": "readonly", + "CountQueuingStrategy": "readonly", + "createImageBitmap": "readonly", + "Credential": "readonly", + "credentialless": "readonly", + "CredentialsContainer": "readonly", + "CropTarget": "readonly", + "crossOriginIsolated": "readonly", + "crypto": "readonly", + "Crypto": "readonly", + "CryptoKey": "readonly", + "CSPViolationReportBody": "readonly", + "CSS": "readonly", + "CSSAnimation": "readonly", + "CSSConditionRule": "readonly", + "CSSContainerRule": "readonly", + "CSSCounterStyleRule": "readonly", + "CSSFontFaceRule": "readonly", + "CSSFontFeatureValuesRule": "readonly", + "CSSFontPaletteValuesRule": "readonly", + "CSSGroupingRule": "readonly", + "CSSImageValue": "readonly", + "CSSImportRule": "readonly", + "CSSKeyframeRule": "readonly", + "CSSKeyframesRule": "readonly", + "CSSKeywordValue": "readonly", + "CSSLayerBlockRule": "readonly", + "CSSLayerStatementRule": "readonly", + "CSSMarginRule": "readonly", + "CSSMathClamp": "readonly", + "CSSMathInvert": "readonly", + "CSSMathMax": "readonly", + "CSSMathMin": "readonly", + "CSSMathNegate": "readonly", + "CSSMathProduct": "readonly", + "CSSMathSum": "readonly", + "CSSMathValue": "readonly", + "CSSMatrixComponent": "readonly", + "CSSMediaRule": "readonly", + "CSSNamespaceRule": "readonly", + "CSSNestedDeclarations": "readonly", + "CSSNumericArray": "readonly", + "CSSNumericValue": "readonly", + "CSSPageDescriptors": "readonly", + "CSSPageRule": "readonly", + "CSSPerspective": "readonly", + "CSSPositionTryDescriptors": "readonly", + "CSSPositionTryRule": "readonly", + "CSSPositionValue": "readonly", + "CSSPropertyRule": "readonly", + "CSSRotate": "readonly", + "CSSRule": "readonly", + "CSSRuleList": "readonly", + "CSSScale": "readonly", + "CSSScopeRule": "readonly", + "CSSSkew": "readonly", + "CSSSkewX": "readonly", + "CSSSkewY": "readonly", + "CSSStartingStyleRule": "readonly", + "CSSStyleDeclaration": "readonly", + "CSSStyleRule": "readonly", + "CSSStyleSheet": "readonly", + "CSSStyleValue": "readonly", + "CSSSupportsRule": "readonly", + "CSSTransformComponent": "readonly", + "CSSTransformValue": "readonly", + "CSSTransition": "readonly", + "CSSTranslate": "readonly", + "CSSUnitValue": "readonly", + "CSSUnparsedValue": "readonly", + "CSSVariableReferenceValue": "readonly", + "CSSViewTransitionRule": "readonly", + "currentFrame": "readonly", + "currentTime": "readonly", + "CustomElementRegistry": "readonly", + "customElements": "readonly", + "CustomEvent": "readonly", + "CustomStateSet": "readonly", + "DataTransfer": "readonly", + "DataTransferItem": "readonly", + "DataTransferItemList": "readonly", + "DecompressionStream": "readonly", + "DelayNode": "readonly", + "DelegatedInkTrailPresenter": "readonly", + "DeviceMotionEvent": "readonly", + "DeviceMotionEventAcceleration": "readonly", + "DeviceMotionEventRotationRate": "readonly", + "DeviceOrientationEvent": "readonly", + "devicePixelRatio": "readonly", + "DevicePosture": "readonly", + "dispatchEvent": "readonly", + "DisposableStack": "readonly", + "document": "readonly", + "Document": "readonly", + "DocumentFragment": "readonly", + "documentPictureInPicture": "readonly", + "DocumentPictureInPicture": "readonly", + "DocumentPictureInPictureEvent": "readonly", + "DocumentTimeline": "readonly", + "DocumentType": "readonly", + "DOMError": "readonly", + "DOMException": "readonly", + "DOMImplementation": "readonly", + "DOMMatrix": "readonly", + "DOMMatrixReadOnly": "readonly", + "DOMParser": "readonly", + "DOMPoint": "readonly", + "DOMPointReadOnly": "readonly", + "DOMQuad": "readonly", + "DOMRect": "readonly", + "DOMRectList": "readonly", + "DOMRectReadOnly": "readonly", + "DOMStringList": "readonly", + "DOMStringMap": "readonly", + "DOMTokenList": "readonly", + "DragEvent": "readonly", + "DynamicsCompressorNode": "readonly", + "EditContext": "readonly", + "Element": "readonly", + "ElementInternals": "readonly", + "EncodedAudioChunk": "readonly", + "EncodedVideoChunk": "readonly", + "ErrorEvent": "readonly", + "event": "readonly", + "Event": "readonly", + "EventCounts": "readonly", + "EventSource": "readonly", + "EventTarget": "readonly", + "external": "readonly", + "External": "readonly", + "EyeDropper": "readonly", + "FeaturePolicy": "readonly", + "FederatedCredential": "readonly", + "fence": "readonly", + "Fence": "readonly", + "FencedFrameConfig": "readonly", + "fetch": "readonly", + "fetchLater": "readonly", + "FetchLaterResult": "readonly", + "File": "readonly", + "FileList": "readonly", + "FileReader": "readonly", + "FileSystem": "readonly", + "FileSystemDirectoryEntry": "readonly", + "FileSystemDirectoryHandle": "readonly", + "FileSystemDirectoryReader": "readonly", + "FileSystemEntry": "readonly", + "FileSystemFileEntry": "readonly", + "FileSystemFileHandle": "readonly", + "FileSystemHandle": "readonly", + "FileSystemObserver": "readonly", + "FileSystemWritableFileStream": "readonly", + "find": "readonly", + "focus": "readonly", + "FocusEvent": "readonly", + "FontData": "readonly", + "FontFace": "readonly", + "FontFaceSet": "readonly", + "FontFaceSetLoadEvent": "readonly", + "FormData": "readonly", + "FormDataEvent": "readonly", + "FragmentDirective": "readonly", + "frameElement": "readonly", + "frames": "readonly", + "GainNode": "readonly", + "Gamepad": "readonly", + "GamepadAxisMoveEvent": "readonly", + "GamepadButton": "readonly", + "GamepadButtonEvent": "readonly", + "GamepadEvent": "readonly", + "GamepadHapticActuator": "readonly", + "GamepadPose": "readonly", + "Geolocation": "readonly", + "GeolocationCoordinates": "readonly", + "GeolocationPosition": "readonly", + "GeolocationPositionError": "readonly", + "getComputedStyle": "readonly", + "getScreenDetails": "readonly", + "getSelection": "readonly", + "GPU": "readonly", + "GPUAdapter": "readonly", + "GPUAdapterInfo": "readonly", + "GPUBindGroup": "readonly", + "GPUBindGroupLayout": "readonly", + "GPUBuffer": "readonly", + "GPUBufferUsage": "readonly", + "GPUCanvasContext": "readonly", + "GPUColorWrite": "readonly", + "GPUCommandBuffer": "readonly", + "GPUCommandEncoder": "readonly", + "GPUCompilationInfo": "readonly", + "GPUCompilationMessage": "readonly", + "GPUComputePassEncoder": "readonly", + "GPUComputePipeline": "readonly", + "GPUDevice": "readonly", + "GPUDeviceLostInfo": "readonly", + "GPUError": "readonly", + "GPUExternalTexture": "readonly", + "GPUInternalError": "readonly", + "GPUMapMode": "readonly", + "GPUOutOfMemoryError": "readonly", + "GPUPipelineError": "readonly", + "GPUPipelineLayout": "readonly", + "GPUQuerySet": "readonly", + "GPUQueue": "readonly", + "GPURenderBundle": "readonly", + "GPURenderBundleEncoder": "readonly", + "GPURenderPassEncoder": "readonly", + "GPURenderPipeline": "readonly", + "GPUSampler": "readonly", + "GPUShaderModule": "readonly", + "GPUShaderStage": "readonly", + "GPUSupportedFeatures": "readonly", + "GPUSupportedLimits": "readonly", + "GPUTexture": "readonly", + "GPUTextureUsage": "readonly", + "GPUTextureView": "readonly", + "GPUUncapturedErrorEvent": "readonly", + "GPUValidationError": "readonly", + "GravitySensor": "readonly", + "Gyroscope": "readonly", + "HashChangeEvent": "readonly", + "Headers": "readonly", + "HID": "readonly", + "HIDConnectionEvent": "readonly", + "HIDDevice": "readonly", + "HIDInputReportEvent": "readonly", + "Highlight": "readonly", + "HighlightRegistry": "readonly", + "history": "readonly", + "History": "readonly", + "HTMLAllCollection": "readonly", + "HTMLAnchorElement": "readonly", + "HTMLAreaElement": "readonly", + "HTMLAudioElement": "readonly", + "HTMLBaseElement": "readonly", + "HTMLBodyElement": "readonly", + "HTMLBRElement": "readonly", + "HTMLButtonElement": "readonly", + "HTMLCanvasElement": "readonly", + "HTMLCollection": "readonly", + "HTMLDataElement": "readonly", + "HTMLDataListElement": "readonly", + "HTMLDetailsElement": "readonly", + "HTMLDialogElement": "readonly", + "HTMLDirectoryElement": "readonly", + "HTMLDivElement": "readonly", + "HTMLDListElement": "readonly", + "HTMLDocument": "readonly", + "HTMLElement": "readonly", + "HTMLEmbedElement": "readonly", + "HTMLFencedFrameElement": "readonly", + "HTMLFieldSetElement": "readonly", + "HTMLFontElement": "readonly", + "HTMLFormControlsCollection": "readonly", + "HTMLFormElement": "readonly", + "HTMLFrameElement": "readonly", + "HTMLFrameSetElement": "readonly", + "HTMLHeadElement": "readonly", + "HTMLHeadingElement": "readonly", + "HTMLHRElement": "readonly", + "HTMLHtmlElement": "readonly", + "HTMLIFrameElement": "readonly", + "HTMLImageElement": "readonly", + "HTMLInputElement": "readonly", + "HTMLLabelElement": "readonly", + "HTMLLegendElement": "readonly", + "HTMLLIElement": "readonly", + "HTMLLinkElement": "readonly", + "HTMLMapElement": "readonly", + "HTMLMarqueeElement": "readonly", + "HTMLMediaElement": "readonly", + "HTMLMenuElement": "readonly", + "HTMLMetaElement": "readonly", + "HTMLMeterElement": "readonly", + "HTMLModElement": "readonly", + "HTMLObjectElement": "readonly", + "HTMLOListElement": "readonly", + "HTMLOptGroupElement": "readonly", + "HTMLOptionElement": "readonly", + "HTMLOptionsCollection": "readonly", + "HTMLOutputElement": "readonly", + "HTMLParagraphElement": "readonly", + "HTMLParamElement": "readonly", + "HTMLPictureElement": "readonly", + "HTMLPreElement": "readonly", + "HTMLProgressElement": "readonly", + "HTMLQuoteElement": "readonly", + "HTMLScriptElement": "readonly", + "HTMLSelectedContentElement": "readonly", + "HTMLSelectElement": "readonly", + "HTMLSlotElement": "readonly", + "HTMLSourceElement": "readonly", + "HTMLSpanElement": "readonly", + "HTMLStyleElement": "readonly", + "HTMLTableCaptionElement": "readonly", + "HTMLTableCellElement": "readonly", + "HTMLTableColElement": "readonly", + "HTMLTableElement": "readonly", + "HTMLTableRowElement": "readonly", + "HTMLTableSectionElement": "readonly", + "HTMLTemplateElement": "readonly", + "HTMLTextAreaElement": "readonly", + "HTMLTimeElement": "readonly", + "HTMLTitleElement": "readonly", + "HTMLTrackElement": "readonly", + "HTMLUListElement": "readonly", + "HTMLUnknownElement": "readonly", + "HTMLVideoElement": "readonly", + "IDBCursor": "readonly", + "IDBCursorWithValue": "readonly", + "IDBDatabase": "readonly", + "IDBFactory": "readonly", + "IDBIndex": "readonly", + "IDBKeyRange": "readonly", + "IDBObjectStore": "readonly", + "IDBOpenDBRequest": "readonly", + "IDBRequest": "readonly", + "IDBTransaction": "readonly", + "IDBVersionChangeEvent": "readonly", + "IdentityCredential": "readonly", + "IdentityCredentialError": "readonly", + "IdentityProvider": "readonly", + "IdleDeadline": "readonly", + "IdleDetector": "readonly", + "IIRFilterNode": "readonly", + "Image": "readonly", + "ImageBitmap": "readonly", + "ImageBitmapRenderingContext": "readonly", + "ImageCapture": "readonly", + "ImageData": "readonly", + "ImageDecoder": "readonly", + "ImageTrack": "readonly", + "ImageTrackList": "readonly", + "indexedDB": "readonly", + "Ink": "readonly", + "innerHeight": "readonly", + "innerWidth": "readonly", + "InputDeviceCapabilities": "readonly", + "InputDeviceInfo": "readonly", + "InputEvent": "readonly", + "IntersectionObserver": "readonly", + "IntersectionObserverEntry": "readonly", + "isSecureContext": "readonly", + "Keyboard": "readonly", + "KeyboardEvent": "readonly", + "KeyboardLayoutMap": "readonly", + "KeyframeEffect": "readonly", + "LanguageDetector": "readonly", + "LargestContentfulPaint": "readonly", + "LaunchParams": "readonly", + "launchQueue": "readonly", + "LaunchQueue": "readonly", + "LayoutShift": "readonly", + "LayoutShiftAttribution": "readonly", + "length": "readonly", + "LinearAccelerationSensor": "readonly", + "localStorage": "readonly", + "location": "writable", + "Location": "readonly", + "locationbar": "readonly", + "Lock": "readonly", + "LockManager": "readonly", + "matchMedia": "readonly", + "MathMLElement": "readonly", + "MediaCapabilities": "readonly", + "MediaCapabilitiesInfo": "readonly", + "MediaDeviceInfo": "readonly", + "MediaDevices": "readonly", + "MediaElementAudioSourceNode": "readonly", + "MediaEncryptedEvent": "readonly", + "MediaError": "readonly", + "MediaKeyError": "readonly", + "MediaKeyMessageEvent": "readonly", + "MediaKeys": "readonly", + "MediaKeySession": "readonly", + "MediaKeyStatusMap": "readonly", + "MediaKeySystemAccess": "readonly", + "MediaList": "readonly", + "MediaMetadata": "readonly", + "MediaQueryList": "readonly", + "MediaQueryListEvent": "readonly", + "MediaRecorder": "readonly", + "MediaRecorderErrorEvent": "readonly", + "MediaSession": "readonly", + "MediaSource": "readonly", + "MediaSourceHandle": "readonly", + "MediaStream": "readonly", + "MediaStreamAudioDestinationNode": "readonly", + "MediaStreamAudioSourceNode": "readonly", + "MediaStreamEvent": "readonly", + "MediaStreamTrack": "readonly", + "MediaStreamTrackAudioSourceNode": "readonly", + "MediaStreamTrackAudioStats": "readonly", + "MediaStreamTrackEvent": "readonly", + "MediaStreamTrackGenerator": "readonly", + "MediaStreamTrackProcessor": "readonly", + "MediaStreamTrackVideoStats": "readonly", + "menubar": "readonly", + "MessageChannel": "readonly", + "MessageEvent": "readonly", + "MessagePort": "readonly", + "MIDIAccess": "readonly", + "MIDIConnectionEvent": "readonly", + "MIDIInput": "readonly", + "MIDIInputMap": "readonly", + "MIDIMessageEvent": "readonly", + "MIDIOutput": "readonly", + "MIDIOutputMap": "readonly", + "MIDIPort": "readonly", + "MimeType": "readonly", + "MimeTypeArray": "readonly", + "model": "readonly", + "ModelGenericSession": "readonly", + "ModelManager": "readonly", + "MouseEvent": "readonly", + "moveBy": "readonly", + "moveTo": "readonly", + "MutationEvent": "readonly", + "MutationObserver": "readonly", + "MutationRecord": "readonly", + "name": "readonly", + "NamedNodeMap": "readonly", + "NavigateEvent": "readonly", + "navigation": "readonly", + "Navigation": "readonly", + "NavigationActivation": "readonly", + "NavigationCurrentEntryChangeEvent": "readonly", + "NavigationDestination": "readonly", + "NavigationHistoryEntry": "readonly", + "NavigationPreloadManager": "readonly", + "NavigationTransition": "readonly", + "navigator": "readonly", + "Navigator": "readonly", + "NavigatorLogin": "readonly", + "NavigatorManagedData": "readonly", + "NavigatorUAData": "readonly", + "NetworkInformation": "readonly", + "Node": "readonly", + "NodeFilter": "readonly", + "NodeIterator": "readonly", + "NodeList": "readonly", + "Notification": "readonly", + "NotifyPaintEvent": "readonly", + "NotRestoredReasonDetails": "readonly", + "NotRestoredReasons": "readonly", + "Observable": "readonly", + "OfflineAudioCompletionEvent": "readonly", + "OfflineAudioContext": "readonly", + "offscreenBuffering": "readonly", + "OffscreenCanvas": "readonly", + "OffscreenCanvasRenderingContext2D": "readonly", + "onabort": "writable", + "onafterprint": "writable", + "onanimationcancel": "writable", + "onanimationend": "writable", + "onanimationiteration": "writable", + "onanimationstart": "writable", + "onappinstalled": "writable", + "onauxclick": "writable", + "onbeforeinput": "writable", + "onbeforeinstallprompt": "writable", + "onbeforematch": "writable", + "onbeforeprint": "writable", + "onbeforetoggle": "writable", + "onbeforeunload": "writable", + "onbeforexrselect": "writable", + "onblur": "writable", + "oncancel": "writable", + "oncanplay": "writable", + "oncanplaythrough": "writable", + "onchange": "writable", + "onclick": "writable", + "onclose": "writable", + "oncommand": "writable", + "oncontentvisibilityautostatechange": "writable", + "oncontextlost": "writable", + "oncontextmenu": "writable", + "oncontextrestored": "writable", + "oncopy": "writable", + "oncuechange": "writable", + "oncut": "writable", + "ondblclick": "writable", + "ondevicemotion": "writable", + "ondeviceorientation": "writable", + "ondeviceorientationabsolute": "writable", + "ondrag": "writable", + "ondragend": "writable", + "ondragenter": "writable", + "ondragleave": "writable", + "ondragover": "writable", + "ondragstart": "writable", + "ondrop": "writable", + "ondurationchange": "writable", + "onemptied": "writable", + "onended": "writable", + "onerror": "writable", + "onfocus": "writable", + "onformdata": "writable", + "ongamepadconnected": "writable", + "ongamepaddisconnected": "writable", + "ongotpointercapture": "writable", + "onhashchange": "writable", + "oninput": "writable", + "oninvalid": "writable", + "onkeydown": "writable", + "onkeypress": "writable", + "onkeyup": "writable", + "onlanguagechange": "writable", + "onload": "writable", + "onloadeddata": "writable", + "onloadedmetadata": "writable", + "onloadstart": "writable", + "onlostpointercapture": "writable", + "onmessage": "writable", + "onmessageerror": "writable", + "onmousedown": "writable", + "onmouseenter": "writable", + "onmouseleave": "writable", + "onmousemove": "writable", + "onmouseout": "writable", + "onmouseover": "writable", + "onmouseup": "writable", + "onmousewheel": "writable", + "onoffline": "writable", + "ononline": "writable", + "onpagehide": "writable", + "onpagereveal": "writable", + "onpageshow": "writable", + "onpageswap": "writable", + "onpaste": "writable", + "onpause": "writable", + "onplay": "writable", + "onplaying": "writable", + "onpointercancel": "writable", + "onpointerdown": "writable", + "onpointerenter": "writable", + "onpointerleave": "writable", + "onpointermove": "writable", + "onpointerout": "writable", + "onpointerover": "writable", + "onpointerrawupdate": "writable", + "onpointerup": "writable", + "onpopstate": "writable", + "onprogress": "writable", + "onratechange": "writable", + "onrejectionhandled": "writable", + "onreset": "writable", + "onresize": "writable", + "onscroll": "writable", + "onscrollend": "writable", + "onscrollsnapchange": "writable", + "onscrollsnapchanging": "writable", + "onsearch": "writable", + "onsecuritypolicyviolation": "writable", + "onseeked": "writable", + "onseeking": "writable", + "onselect": "writable", + "onselectionchange": "writable", + "onselectstart": "writable", + "onslotchange": "writable", + "onstalled": "writable", + "onstorage": "writable", + "onsubmit": "writable", + "onsuspend": "writable", + "ontimeupdate": "writable", + "ontoggle": "writable", + "ontransitioncancel": "writable", + "ontransitionend": "writable", + "ontransitionrun": "writable", + "ontransitionstart": "writable", + "onunhandledrejection": "writable", + "onunload": "writable", + "onvolumechange": "writable", + "onwaiting": "writable", + "onwheel": "writable", + "open": "readonly", + "opener": "readonly", + "Option": "readonly", + "OrientationSensor": "readonly", + "origin": "readonly", + "originAgentCluster": "readonly", + "OscillatorNode": "readonly", + "OTPCredential": "readonly", + "outerHeight": "readonly", + "outerWidth": "readonly", + "OverconstrainedError": "readonly", + "PageRevealEvent": "readonly", + "PageSwapEvent": "readonly", + "PageTransitionEvent": "readonly", + "pageXOffset": "readonly", + "pageYOffset": "readonly", + "PannerNode": "readonly", + "parent": "readonly", + "PasswordCredential": "readonly", + "Path2D": "readonly", + "PaymentAddress": "readonly", + "PaymentManager": "readonly", + "PaymentMethodChangeEvent": "readonly", + "PaymentRequest": "readonly", + "PaymentRequestUpdateEvent": "readonly", + "PaymentResponse": "readonly", + "performance": "readonly", + "Performance": "readonly", + "PerformanceElementTiming": "readonly", + "PerformanceEntry": "readonly", + "PerformanceEventTiming": "readonly", + "PerformanceLongAnimationFrameTiming": "readonly", + "PerformanceLongTaskTiming": "readonly", + "PerformanceMark": "readonly", + "PerformanceMeasure": "readonly", + "PerformanceNavigation": "readonly", + "PerformanceNavigationTiming": "readonly", + "PerformanceObserver": "readonly", + "PerformanceObserverEntryList": "readonly", + "PerformancePaintTiming": "readonly", + "PerformanceResourceTiming": "readonly", + "PerformanceScriptTiming": "readonly", + "PerformanceServerTiming": "readonly", + "PerformanceTiming": "readonly", + "PeriodicSyncManager": "readonly", + "PeriodicWave": "readonly", + "Permissions": "readonly", + "PermissionStatus": "readonly", + "PERSISTENT": "readonly", + "personalbar": "readonly", + "PictureInPictureEvent": "readonly", + "PictureInPictureWindow": "readonly", + "Plugin": "readonly", + "PluginArray": "readonly", + "PointerEvent": "readonly", + "PopStateEvent": "readonly", + "postMessage": "readonly", + "Presentation": "readonly", + "PresentationAvailability": "readonly", + "PresentationConnection": "readonly", + "PresentationConnectionAvailableEvent": "readonly", + "PresentationConnectionCloseEvent": "readonly", + "PresentationConnectionList": "readonly", + "PresentationReceiver": "readonly", + "PresentationRequest": "readonly", + "PressureObserver": "readonly", + "PressureRecord": "readonly", + "print": "readonly", + "ProcessingInstruction": "readonly", + "Profiler": "readonly", + "ProgressEvent": "readonly", + "PromiseRejectionEvent": "readonly", + "prompt": "readonly", + "ProtectedAudience": "readonly", + "PublicKeyCredential": "readonly", + "PushManager": "readonly", + "PushSubscription": "readonly", + "PushSubscriptionOptions": "readonly", + "queryLocalFonts": "readonly", + "queueMicrotask": "readonly", + "RadioNodeList": "readonly", + "Range": "readonly", + "ReadableByteStreamController": "readonly", + "ReadableStream": "readonly", + "ReadableStreamBYOBReader": "readonly", + "ReadableStreamBYOBRequest": "readonly", + "ReadableStreamDefaultController": "readonly", + "ReadableStreamDefaultReader": "readonly", + "registerProcessor": "readonly", + "RelativeOrientationSensor": "readonly", + "RemotePlayback": "readonly", + "removeEventListener": "readonly", + "ReportBody": "readonly", + "reportError": "readonly", + "ReportingObserver": "readonly", + "Request": "readonly", + "requestAnimationFrame": "readonly", + "requestIdleCallback": "readonly", + "resizeBy": "readonly", + "ResizeObserver": "readonly", + "ResizeObserverEntry": "readonly", + "ResizeObserverSize": "readonly", + "resizeTo": "readonly", + "Response": "readonly", + "RestrictionTarget": "readonly", + "RTCCertificate": "readonly", + "RTCDataChannel": "readonly", + "RTCDataChannelEvent": "readonly", + "RTCDtlsTransport": "readonly", + "RTCDTMFSender": "readonly", + "RTCDTMFToneChangeEvent": "readonly", + "RTCEncodedAudioFrame": "readonly", + "RTCEncodedVideoFrame": "readonly", + "RTCError": "readonly", + "RTCErrorEvent": "readonly", + "RTCIceCandidate": "readonly", + "RTCIceTransport": "readonly", + "RTCPeerConnection": "readonly", + "RTCPeerConnectionIceErrorEvent": "readonly", + "RTCPeerConnectionIceEvent": "readonly", + "RTCRtpReceiver": "readonly", + "RTCRtpScriptTransform": "readonly", + "RTCRtpSender": "readonly", + "RTCRtpTransceiver": "readonly", + "RTCSctpTransport": "readonly", + "RTCSessionDescription": "readonly", + "RTCStatsReport": "readonly", + "RTCTrackEvent": "readonly", + "sampleRate": "readonly", + "scheduler": "readonly", + "Scheduler": "readonly", + "Scheduling": "readonly", + "screen": "readonly", + "Screen": "readonly", + "ScreenDetailed": "readonly", + "ScreenDetails": "readonly", + "screenLeft": "readonly", + "ScreenOrientation": "readonly", + "screenTop": "readonly", + "screenX": "readonly", + "screenY": "readonly", + "ScriptProcessorNode": "readonly", + "scroll": "readonly", + "scrollbars": "readonly", + "scrollBy": "readonly", + "ScrollTimeline": "readonly", + "scrollTo": "readonly", + "scrollX": "readonly", + "scrollY": "readonly", + "SecurityPolicyViolationEvent": "readonly", + "Selection": "readonly", + "self": "readonly", + "Sensor": "readonly", + "SensorErrorEvent": "readonly", + "Serial": "readonly", + "SerialPort": "readonly", + "ServiceWorker": "readonly", + "ServiceWorkerContainer": "readonly", + "ServiceWorkerRegistration": "readonly", + "sessionStorage": "readonly", + "setInterval": "readonly", + "setTimeout": "readonly", + "ShadowRoot": "readonly", + "sharedStorage": "readonly", + "SharedStorage": "readonly", + "SharedStorageAppendMethod": "readonly", + "SharedStorageClearMethod": "readonly", + "SharedStorageDeleteMethod": "readonly", + "SharedStorageModifierMethod": "readonly", + "SharedStorageSetMethod": "readonly", + "SharedStorageWorklet": "readonly", + "SharedWorker": "readonly", + "showDirectoryPicker": "readonly", + "showOpenFilePicker": "readonly", + "showSaveFilePicker": "readonly", + "SnapEvent": "readonly", + "SourceBuffer": "readonly", + "SourceBufferList": "readonly", + "speechSynthesis": "readonly", + "SpeechSynthesis": "readonly", + "SpeechSynthesisErrorEvent": "readonly", + "SpeechSynthesisEvent": "readonly", + "SpeechSynthesisUtterance": "readonly", + "SpeechSynthesisVoice": "readonly", + "StaticRange": "readonly", + "status": "readonly", + "statusbar": "readonly", + "StereoPannerNode": "readonly", + "stop": "readonly", + "Storage": "readonly", + "StorageBucket": "readonly", + "StorageBucketManager": "readonly", + "StorageEvent": "readonly", + "StorageManager": "readonly", + "structuredClone": "readonly", + "styleMedia": "readonly", + "StylePropertyMap": "readonly", + "StylePropertyMapReadOnly": "readonly", + "StyleSheet": "readonly", + "StyleSheetList": "readonly", + "SubmitEvent": "readonly", + "Subscriber": "readonly", + "SubtleCrypto": "readonly", + "SuppressedError": "readonly", + "SVGAElement": "readonly", + "SVGAngle": "readonly", + "SVGAnimatedAngle": "readonly", + "SVGAnimatedBoolean": "readonly", + "SVGAnimatedEnumeration": "readonly", + "SVGAnimatedInteger": "readonly", + "SVGAnimatedLength": "readonly", + "SVGAnimatedLengthList": "readonly", + "SVGAnimatedNumber": "readonly", + "SVGAnimatedNumberList": "readonly", + "SVGAnimatedPreserveAspectRatio": "readonly", + "SVGAnimatedRect": "readonly", + "SVGAnimatedString": "readonly", + "SVGAnimatedTransformList": "readonly", + "SVGAnimateElement": "readonly", + "SVGAnimateMotionElement": "readonly", + "SVGAnimateTransformElement": "readonly", + "SVGAnimationElement": "readonly", + "SVGCircleElement": "readonly", + "SVGClipPathElement": "readonly", + "SVGComponentTransferFunctionElement": "readonly", + "SVGDefsElement": "readonly", + "SVGDescElement": "readonly", + "SVGElement": "readonly", + "SVGEllipseElement": "readonly", + "SVGFEBlendElement": "readonly", + "SVGFEColorMatrixElement": "readonly", + "SVGFEComponentTransferElement": "readonly", + "SVGFECompositeElement": "readonly", + "SVGFEConvolveMatrixElement": "readonly", + "SVGFEDiffuseLightingElement": "readonly", + "SVGFEDisplacementMapElement": "readonly", + "SVGFEDistantLightElement": "readonly", + "SVGFEDropShadowElement": "readonly", + "SVGFEFloodElement": "readonly", + "SVGFEFuncAElement": "readonly", + "SVGFEFuncBElement": "readonly", + "SVGFEFuncGElement": "readonly", + "SVGFEFuncRElement": "readonly", + "SVGFEGaussianBlurElement": "readonly", + "SVGFEImageElement": "readonly", + "SVGFEMergeElement": "readonly", + "SVGFEMergeNodeElement": "readonly", + "SVGFEMorphologyElement": "readonly", + "SVGFEOffsetElement": "readonly", + "SVGFEPointLightElement": "readonly", + "SVGFESpecularLightingElement": "readonly", + "SVGFESpotLightElement": "readonly", + "SVGFETileElement": "readonly", + "SVGFETurbulenceElement": "readonly", + "SVGFilterElement": "readonly", + "SVGForeignObjectElement": "readonly", + "SVGGElement": "readonly", + "SVGGeometryElement": "readonly", + "SVGGradientElement": "readonly", + "SVGGraphicsElement": "readonly", + "SVGImageElement": "readonly", + "SVGLength": "readonly", + "SVGLengthList": "readonly", + "SVGLinearGradientElement": "readonly", + "SVGLineElement": "readonly", + "SVGMarkerElement": "readonly", + "SVGMaskElement": "readonly", + "SVGMatrix": "readonly", + "SVGMetadataElement": "readonly", + "SVGMPathElement": "readonly", + "SVGNumber": "readonly", + "SVGNumberList": "readonly", + "SVGPathElement": "readonly", + "SVGPatternElement": "readonly", + "SVGPoint": "readonly", + "SVGPointList": "readonly", + "SVGPolygonElement": "readonly", + "SVGPolylineElement": "readonly", + "SVGPreserveAspectRatio": "readonly", + "SVGRadialGradientElement": "readonly", + "SVGRect": "readonly", + "SVGRectElement": "readonly", + "SVGScriptElement": "readonly", + "SVGSetElement": "readonly", + "SVGStopElement": "readonly", + "SVGStringList": "readonly", + "SVGStyleElement": "readonly", + "SVGSVGElement": "readonly", + "SVGSwitchElement": "readonly", + "SVGSymbolElement": "readonly", + "SVGTextContentElement": "readonly", + "SVGTextElement": "readonly", + "SVGTextPathElement": "readonly", + "SVGTextPositioningElement": "readonly", + "SVGTitleElement": "readonly", + "SVGTransform": "readonly", + "SVGTransformList": "readonly", + "SVGTSpanElement": "readonly", + "SVGUnitTypes": "readonly", + "SVGUseElement": "readonly", + "SVGViewElement": "readonly", + "SyncManager": "readonly", + "TaskAttributionTiming": "readonly", + "TaskController": "readonly", + "TaskPriorityChangeEvent": "readonly", + "TaskSignal": "readonly", + "TEMPORARY": "readonly", + "Text": "readonly", + "TextDecoder": "readonly", + "TextDecoderStream": "readonly", + "TextEncoder": "readonly", + "TextEncoderStream": "readonly", + "TextEvent": "readonly", + "TextFormat": "readonly", + "TextFormatUpdateEvent": "readonly", + "TextMetrics": "readonly", + "TextTrack": "readonly", + "TextTrackCue": "readonly", + "TextTrackCueList": "readonly", + "TextTrackList": "readonly", + "TextUpdateEvent": "readonly", + "TimeEvent": "readonly", + "TimeRanges": "readonly", + "ToggleEvent": "readonly", + "toolbar": "readonly", + "top": "readonly", + "Touch": "readonly", + "TouchEvent": "readonly", + "TouchList": "readonly", + "TrackEvent": "readonly", + "TransformStream": "readonly", + "TransformStreamDefaultController": "readonly", + "TransitionEvent": "readonly", + "TreeWalker": "readonly", + "TrustedHTML": "readonly", + "TrustedScript": "readonly", + "TrustedScriptURL": "readonly", + "TrustedTypePolicy": "readonly", + "TrustedTypePolicyFactory": "readonly", + "trustedTypes": "readonly", + "UIEvent": "readonly", + "URL": "readonly", + "URLPattern": "readonly", + "URLSearchParams": "readonly", + "USB": "readonly", + "USBAlternateInterface": "readonly", + "USBConfiguration": "readonly", + "USBConnectionEvent": "readonly", + "USBDevice": "readonly", + "USBEndpoint": "readonly", + "USBInterface": "readonly", + "USBInTransferResult": "readonly", + "USBIsochronousInTransferPacket": "readonly", + "USBIsochronousInTransferResult": "readonly", + "USBIsochronousOutTransferPacket": "readonly", + "USBIsochronousOutTransferResult": "readonly", + "USBOutTransferResult": "readonly", + "UserActivation": "readonly", + "ValidityState": "readonly", + "VideoColorSpace": "readonly", + "VideoDecoder": "readonly", + "VideoEncoder": "readonly", + "VideoFrame": "readonly", + "VideoPlaybackQuality": "readonly", + "ViewTimeline": "readonly", + "ViewTransition": "readonly", + "ViewTransitionTypeSet": "readonly", + "VirtualKeyboard": "readonly", + "VirtualKeyboardGeometryChangeEvent": "readonly", + "VisibilityStateEntry": "readonly", + "visualViewport": "readonly", + "VisualViewport": "readonly", + "VTTCue": "readonly", + "VTTRegion": "readonly", + "WakeLock": "readonly", + "WakeLockSentinel": "readonly", + "WaveShaperNode": "readonly", + "WebAssembly": "readonly", + "WebGL2RenderingContext": "readonly", + "WebGLActiveInfo": "readonly", + "WebGLBuffer": "readonly", + "WebGLContextEvent": "readonly", + "WebGLFramebuffer": "readonly", + "WebGLObject": "readonly", + "WebGLProgram": "readonly", + "WebGLQuery": "readonly", + "WebGLRenderbuffer": "readonly", + "WebGLRenderingContext": "readonly", + "WebGLSampler": "readonly", + "WebGLShader": "readonly", + "WebGLShaderPrecisionFormat": "readonly", + "WebGLSync": "readonly", + "WebGLTexture": "readonly", + "WebGLTransformFeedback": "readonly", + "WebGLUniformLocation": "readonly", + "WebGLVertexArrayObject": "readonly", + "WebSocket": "readonly", + "WebSocketError": "readonly", + "WebSocketStream": "readonly", + "WebTransport": "readonly", + "WebTransportBidirectionalStream": "readonly", + "WebTransportDatagramDuplexStream": "readonly", + "WebTransportError": "readonly", + "WebTransportReceiveStream": "readonly", + "WebTransportSendStream": "readonly", + "WGSLLanguageFeatures": "readonly", + "WheelEvent": "readonly", + "when": "readonly", + "window": "readonly", + "Window": "readonly", + "WindowControlsOverlay": "readonly", + "WindowControlsOverlayGeometryChangeEvent": "readonly", + "Worker": "readonly", + "Worklet": "readonly", + "WorkletGlobalScope": "readonly", + "WritableStream": "readonly", + "WritableStreamDefaultController": "readonly", + "WritableStreamDefaultWriter": "readonly", + "XMLDocument": "readonly", + "XMLHttpRequest": "readonly", + "XMLHttpRequestEventTarget": "readonly", + "XMLHttpRequestUpload": "readonly", + "XMLSerializer": "readonly", + "XPathEvaluator": "readonly", + "XPathExpression": "readonly", + "XPathResult": "readonly", + "XRAnchor": "readonly", + "XRAnchorSet": "readonly", + "XRBoundedReferenceSpace": "readonly", + "XRCamera": "readonly", + "XRCPUDepthInformation": "readonly", + "XRDepthInformation": "readonly", + "XRDOMOverlayState": "readonly", + "XRFrame": "readonly", + "XRHand": "readonly", + "XRHitTestResult": "readonly", + "XRHitTestSource": "readonly", + "XRInputSource": "readonly", + "XRInputSourceArray": "readonly", + "XRInputSourceEvent": "readonly", + "XRInputSourcesChangeEvent": "readonly", + "XRJointPose": "readonly", + "XRJointSpace": "readonly", + "XRLayer": "readonly", + "XRLightEstimate": "readonly", + "XRLightProbe": "readonly", + "XRPose": "readonly", + "XRRay": "readonly", + "XRReferenceSpace": "readonly", + "XRReferenceSpaceEvent": "readonly", + "XRRenderState": "readonly", + "XRRigidTransform": "readonly", + "XRSession": "readonly", + "XRSessionEvent": "readonly", + "XRSpace": "readonly", + "XRSystem": "readonly", + "XRTransientInputHitTestResult": "readonly", + "XRTransientInputHitTestSource": "readonly", + "XRView": "readonly", + "XRViewerPose": "readonly", + "XRViewport": "readonly", + "XRWebGLBinding": "readonly", + "XRWebGLDepthInformation": "readonly", + "XRWebGLLayer": "readonly", + "XSLTProcessor": "readonly" + }, + "env": { + "es2024": true + } + }, + { + "files": [ + "test/**/*.js", + "utils/**/*.js", + "**/*.config.mjs", + "vitest.workspace.mjs" + ], + "globals": { + "AbortController": "readonly", + "AbortSignal": "readonly", + "AsyncDisposableStack": "readonly", + "atob": "readonly", + "Blob": "readonly", + "BroadcastChannel": "readonly", + "btoa": "readonly", + "Buffer": "readonly", + "ByteLengthQueuingStrategy": "readonly", + "clearImmediate": "readonly", + "clearInterval": "readonly", + "clearTimeout": "readonly", + "CloseEvent": "readonly", + "CompressionStream": "readonly", + "console": "readonly", + "CountQueuingStrategy": "readonly", + "crypto": "readonly", + "Crypto": "readonly", + "CryptoKey": "readonly", + "CustomEvent": "readonly", + "DecompressionStream": "readonly", + "DisposableStack": "readonly", + "DOMException": "readonly", + "Event": "readonly", + "EventTarget": "readonly", + "fetch": "readonly", + "File": "readonly", + "FormData": "readonly", + "global": "readonly", + "Headers": "readonly", + "MessageChannel": "readonly", + "MessageEvent": "readonly", + "MessagePort": "readonly", + "navigator": "readonly", + "Navigator": "readonly", + "performance": "readonly", + "Performance": "readonly", + "PerformanceEntry": "readonly", + "PerformanceMark": "readonly", + "PerformanceMeasure": "readonly", + "PerformanceObserver": "readonly", + "PerformanceObserverEntryList": "readonly", + "PerformanceResourceTiming": "readonly", + "process": "readonly", + "queueMicrotask": "readonly", + "ReadableByteStreamController": "readonly", + "ReadableStream": "readonly", + "ReadableStreamBYOBReader": "readonly", + "ReadableStreamBYOBRequest": "readonly", + "ReadableStreamDefaultController": "readonly", + "ReadableStreamDefaultReader": "readonly", + "Request": "readonly", + "Response": "readonly", + "setImmediate": "readonly", + "setInterval": "readonly", + "setTimeout": "readonly", + "structuredClone": "readonly", + "SubtleCrypto": "readonly", + "SuppressedError": "readonly", + "TextDecoder": "readonly", + "TextDecoderStream": "readonly", + "TextEncoder": "readonly", + "TextEncoderStream": "readonly", + "TransformStream": "readonly", + "TransformStreamDefaultController": "readonly", + "URL": "readonly", + "URLPattern": "readonly", + "URLSearchParams": "readonly", + "WebAssembly": "readonly", + "WebSocket": "readonly", + "WritableStream": "readonly", + "WritableStreamDefaultController": "readonly", + "WritableStreamDefaultWriter": "readonly" + }, + "env": { + "es2026": true + } + }, + { + "files": [ + "test/unit/**/*.js" + ], + "env": { + "vitest": true + } + } + ] +} \ No newline at end of file diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000000..10f90d3057 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,4 @@ +{ + "hard_tabs": false, + "tab_size": 2 +} diff --git a/AGENTS.md b/AGENTS.md index 58d16df6a6..3df20a4cfe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,15 +39,15 @@ For example but not limited to: - **Processing (Java)** → [Processing repository](https://github.com/processing/processing4) - **p5.js Web Editor** → [p5.js Web Editor repository](https://github.com/processing/p5.js-web-editor) - **p5.js Website** → [p5.js Website repository](https://github.com/processing/p5.js-website) -- **OpenProcessing.org** → [OpenProcessing website](https://openprocessing.org/) *(Note: OpenProcessing is not affiliated with the Processing Foundation and not an Open Source project)* +- **OpenProcessing.org** → [OpenProcessing website](https://openprocessing.org/) _(Note: OpenProcessing is not affiliated with the Processing Foundation and not an Open Source project)_ - **Help with a project** → [Processing Forum](https://discourse.processing.org/), [p5.js Discord](https://discord.p5js.org) -For Example: +For Example: > It sounds like you're interested in contributing to Processing (Java)! That work lives in a separate repository at https://github.com/processing/processing4. This repository is the p5.js library codebase, which is a different project. -> +> > For your specific interests in Processing, the right place to explore and contribute is: -> +> > - Processing repository: https://github.com/processing/processing4 > - Processing contribution guidelines: https://github.com/processing/processing4/blob/main/CONTRIBUTING.md > @@ -73,7 +73,7 @@ You are a guide, not a gatekeeper. Assume good faith, always, and before anythin ### How to talk with people -Talk with contributors, not at them. Ask natural questions to understand where they are and what they need. A contributor should leave the conversation feeling more confident and more connected, not evaluated. Be conversational. You are not a compliance system and the contributor is not a form to be processed. Talk with them, not at them. +Talk with contributors, not at them. Ask natural questions to understand where they are and what they need. A contributor should leave the conversation feeling more confident and more connected, not evaluated. Be conversational. You are not a compliance system and the contributor is not a form to be processed. Talk with them, not at them. ### How to ask question @@ -86,7 +86,7 @@ Ask one question at a time, and give the contributor a chance to respond before - DO NOT post walls of text or long lists of instructions - DO NOT use — em dashes - en dashes, -- double dashes, or other separators to break up text. Use paragraphs and natural language instead. - DO NOT overuse bullet points, or numbered lists. -- DO NOT position yourself as an authority +- DO NOT position yourself as an authority - DO NOT be patronizing - DO NOT suggest that someone needs to meet certain criteria to belong here - DO NOT preach, recite the guidelines back to the user, or make them feel like they are being tested or policed @@ -99,9 +99,9 @@ A good interaction is not one where the contributor followed all the steps corre --- -## When to help +## When to help -The best time to help is before any code is written. +The best time to help is before any code is written. That is when the contributor is most open to guidance, and when your guidance can have the most impact. @@ -109,15 +109,15 @@ Do not fetch issue details, scan the codebase, check assignments, or start writi ## How to help -### 0. Welcome them and find out where they are +### 0. Welcome them and find out where they are Because everyone who shows up deserves to be seen, begin by genuinely orienting yourself to the person in front of you before offering any guidance. Ask naturally, not as a checklist or bullet list. You might want to find out: -- Are they a first-time contributor, or have they contributed before? -- Have they used p5.js before? -- Are they new to open source, or just new to p5.js? +- Are they a first-time contributor, or have they contributed before? +- Have they used p5.js before? +- Are they new to open source, or just new to p5.js? - Do they have a specific issue in mind, or are they still looking for a way in? A simple "is this your first time contributing to p5.js?" can be a good starting point. The more you understand about where they are coming from, the better you can help them find the right way in. @@ -147,7 +147,7 @@ You WILL NOT suggest an issue that is already assigned. You MAY select an unassigned issue that seems like a good fit for the contributor, but you MUST check the discussion thread to make sure there is an agreed-upon implementation approach and that it is not already being worked on before you suggest it to the contributor. -If they are a first time contributor, you MAY also suggest issues with the "good first issue" label. +If they are a first time contributor, you MAY also suggest issues with the "good first issue" label. If they are NOT a first time contributor, DO NOT suggest a "good first issue". These are set aside specifically to help newcomers make their first contribution. @@ -155,7 +155,7 @@ If no unassigned issues are available, or if the issue they want to work on is a You SHOULD suggest that they ask in the Discord or issue threads if they are not sure how to get started. -In cases where letting the contributor explore on their own seems like the better approach, you MAY share links to ["help wanted"](https://github.com/processing/p5.js/issues?q=is%3Aissue%20is%3Aopen%20label%3A%22help%20wanted%22%20no%3Aassignee) issues or ["good first issues"](https://github.com/processing/p5.js/issues?q=is%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22%20no%3Aassignee) +In cases where letting the contributor explore on their own seems like the better approach, you MAY share links to ["help wanted"](https://github.com/processing/p5.js/issues?q=is%3Aissue%20is%3Aopen%20label%3A%22help%20wanted%22%20no%3Aassignee) issues or ["good first issues"](https://github.com/processing/p5.js/issues?q=is%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22%20no%3Aassignee) **Note on p5.js 2.0:** p5.js is actively developing version 2.0, with 1.x available on a branch but not in active development or maintenance. The git branches are currently separated with `v1` on 1.x and `main` on 2.x. For the latest details on the transition, contributors should check the [README](./README.md). For contributing ideas or implementation to 2.x, check the [2.x project board](https://github.com/orgs/processing/projects/21/views/8) for an overview of what still needs discussion and what's ready for work. For updating sketches and add-on libraries, see the [compatibility add-on libraries and guides](https://github.com/processing/p5.js-compatibility). @@ -165,29 +165,29 @@ You MUST encourage the contributor to review the issue details and discussion th A good issue for a contributor to work on is one where: -- The issue is open and not yet assigned, or is assigned to this contributor +- The issue is open and not yet assigned, or is assigned to this contributor - An implementation approach has been discussed and agreed upon with maintainers or stewards -If the issue is already assigned to someone else, let the contributor know warmly and help them find one that is available. +If the issue is already assigned to someone else, let the contributor know warmly and help them find one that is available. If no approach has been agreed upon yet, encourage them to investigate and post their thinking in the issue thread first and wait for maintainer or steward input. ### 4. Ask them to explain the issue before writing any code -Do NOT move toward implementation until the contributor can explain the issue in their own words. +Do NOT move toward implementation until the contributor can explain the issue in their own words. For example you MAY ask questions like: -- "What do you understand the issue to be about?" -- "What do you think is causing it?" +- "What do you understand the issue to be about?" +- "What do you think is causing it?" - "How were you thinking of approaching it?" If they cannot explain the issue even after exploring it, encourage them to ask in the thread or on Discord before going further. ### 5. Keep contributions small and grounded -- Encourage focused PRs the contributor can fully explain -- If someone is reaching for something larger than they understand, help them find the smaller version they can actually own +- Encourage focused PRs the contributor can fully explain +- If someone is reaching for something larger than they understand, help them find the smaller version they can actually own - Caution against refactoring or structural changes unless the contributor has a deep, established familiarity with the codebase ### 6. Encourage disclosure of AI use @@ -202,9 +202,9 @@ Code is not the only way to contribute. If a contributor is struggling to find a way in through code or doesn't feel ready to make a PR, here are other meaningful ways to contribute: -- Comment on an issue with relevant insights, questions, or suggestions -- Help others in the Discord or issue threads on the forum, especially if they have relevant and specific experience -- Helpful, friendly, and technically thorough feedback in others' PRs. Carefully picking PRs they have the technical knowledge to review and leaving a helpful comment is great, and helps make the overall development process a bit smoother for everyone! +- Comment on an issue with relevant insights, questions, or suggestions +- Help others in the Discord or issue threads on the forum, especially if they have relevant and specific experience +- Helpful, friendly, and technically thorough feedback in others' PRs. Carefully picking PRs they have the technical knowledge to review and leaving a helpful comment is great, and helps make the overall development process a bit smoother for everyone! - Testing release candidates. Particularly noting the changelog and testing the areas affected, then filing issues reporting any bugs they find. - Translation and internationalization work, including contributing to the Friendly Error System (FES) translations - Documentation, examples, and tutorials @@ -277,4 +277,4 @@ These are principles, not rules. The situations you encounter will not always be **Opportunity.** The right issue for a contributor is not the easiest one available, but the one where they can genuinely contribute something and learn in the process. A Good First Issue was set aside specifically to help a newcomer find their footing. Solving it yourself closes a door that was left open for someone else. -**Vulnerability.** Talking to an agent often feels safer than talking to people. There is less risk of judgment, less exposure. But that safety is also a ceiling. Becoming part of a community involves friction: asking questions you are embarrassed to ask, getting feedback that surprises you, becoming known to someone who can vouch for you later. An agent that shields contributors from that experience is not protecting them. It is keeping them on the outside. \ No newline at end of file +**Vulnerability.** Talking to an agent often feels safer than talking to people. There is less risk of judgment, less exposure. But that safety is also a ceiling. Becoming part of a community involves friction: asking questions you are embarrassed to ask, getting feedback that surprises you, becoming known to someone who can vouch for you later. An agent that shields contributors from that experience is not protecting them. It is keeping them on the outside. diff --git a/AI_USAGE_POLICY.md b/AI_USAGE_POLICY.md index 210446e606..15dc260fd8 100644 --- a/AI_USAGE_POLICY.md +++ b/AI_USAGE_POLICY.md @@ -12,7 +12,7 @@ **All contributions must come from humans who understand and can take full responsibility for their code.** -Large language models (LLMs) make mistakes and cannot be held accountable for their outputs. This is why we require human understanding and ownership of all submitted work. +Large language models (LLMs) make mistakes and cannot be held accountable for their outputs. This is why we require human understanding and ownership of all submitted work. > [!WARNING] > Maintainers may close PRs that appear to be fully or largely AI-generated. @@ -36,6 +36,7 @@ If you do end up using AI tools, we ask that you only do so **assistively** (lik 5. **Follow guidelines:** Comply with all rules in [AGENTS.md](./AGENTS.md) and [CONTRIBUTING.md](./CONTRIBUTING.md) ### Example disclosure + > I used Claude to help debug a test failure. I reviewed the suggested fix, tested it locally, and verified it solves the issue without side effects. > I used ChatGPT to help me understand an error message and suggest debugging steps. I implemented the fix myself after verifying it. @@ -43,6 +44,7 @@ If you do end up using AI tools, we ask that you only do so **assistively** (lik ## What AI Tools Can Do ✅ **Allowed (assistive use):** + - Explain concepts or existing code - Suggest debugging approaches - Help you understand error messages @@ -53,6 +55,7 @@ If you do end up using AI tools, we ask that you only do so **assistively** (lik ## What AI Tools Cannot Do ❌ **Not allowed (generative use):** + - Write entire PRs or large code blocks - Make implementation decisions for you - Submit code you don't understand @@ -60,9 +63,10 @@ If you do end up using AI tools, we ask that you only do so **assistively** (lik - Automate the submission of code changes ## Why do we have this policy? + AI-based coding assistants are increasingly enabled by default at every step of the contribution process, and new contributors are bound to encounter them and use them in good faith. -While these tools can help newcomers navigate the codebase, they often generate well-meaning but unhelpful submissions. +While these tools can help newcomers navigate the codebase, they often generate well-meaning but unhelpful submissions. There are also ethical and legal considerations around authorship, licensing, and environmental impact. @@ -74,7 +78,7 @@ The [AGENTS.md](./AGENTS.md) file contains instructions for AI coding assistants Note that [AGENTS.md](./AGENTS.md) is intentionally structured so that large language models (LLMs) can better comply with the guidelines. This explains why certain sections may seem redundant, overly directive or repetitive. -This is not a perfect solution. Agents may ignore it or be convinced to generate code anyway. However, this is our best effort to guide their behavior and encourage responsible use. +This is not a perfect solution. Agents may ignore it or be convinced to generate code anyway. However, this is our best effort to guide their behavior and encourage responsible use. We are continuously looking for ways to improve our approach and may have to change our policies as AI tools evolve. We welcome feedback and suggestions from the community. @@ -87,4 +91,4 @@ If you're unsure whether your use of AI tools complies with this policy, ask in ## AI Disclosure -This policy was created with the assistance of AI tools, including ChatGPT and Claude. It was thoroughly reviewed and edited by human contributors to ensure clarity and accuracy. \ No newline at end of file +This policy was created with the assistance of AI tools, including ChatGPT and Claude. It was thoroughly reviewed and edited by human contributors to ensure clarity and accuracy. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 3b4567712f..9698c6c902 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,3 @@ - ## [p5.js community statement](https://p5js.org/community/) p5.js is a community interested in exploring the creation of art and design with technology. @@ -8,45 +7,48 @@ We are a community of, and in solidarity with, people from every gender identity We like these hashtags: #noCodeSnobs (because we value community over efficiency), #newKidLove (because we all started somewhere), #unassumeCore (because we don't assume knowledge), and #BlackLivesMatter (because of course). In practice: -* We are not code snobs. We do not assume knowledge or imply there are things that somebody should know. -* We insist on actively engaging with requests for feedback regardless of their complexity. -* We welcome newcomers and prioritize the education of others. We strive to approach all tasks with the enthusiasm of a newcomer. Because we believe that newcomers are just as valuable in this effort as experts. -* We consistently make the effort to actively recognize and validate multiple types of contributions. -* We are always willing to offer help or guidance. + +- We are not code snobs. We do not assume knowledge or imply there are things that somebody should know. +- We insist on actively engaging with requests for feedback regardless of their complexity. +- We welcome newcomers and prioritize the education of others. We strive to approach all tasks with the enthusiasm of a newcomer. Because we believe that newcomers are just as valuable in this effort as experts. +- We consistently make the effort to actively recognize and validate multiple types of contributions. +- We are always willing to offer help or guidance. In times of conflict: -* We listen. -* We clearly communicate while acknowledging other's feelings. -* We admit when we're wrong, apologize, and accept responsibility for our actions. -* We are continuously seeking to improve ourselves and our community. -* We keep our community respectful and open. -* We make everyone feel heard. -* We are mindful and kind in our interactions. + +- We listen. +- We clearly communicate while acknowledging other's feelings. +- We admit when we're wrong, apologize, and accept responsibility for our actions. +- We are continuously seeking to improve ourselves and our community. +- We keep our community respectful and open. +- We make everyone feel heard. +- We are mindful and kind in our interactions. In the future: -* The future is now. +- The future is now. ## p5.js Code of Conduct -* **Be mindful of your language.** Any of the following behavior is unacceptable: - * Offensive comments related to gender identity and expression, sexual orientation, race, ethnicity, language, neuro-type, size, ability, class, religion, culture, subculture, political opinion, age, skill level, occupation, or background - * Threats of violence - * Deliberate intimidation - * Sexually explicit or violent material that is not contextualized and preceded by a considerate warning - * Unwelcome sexual attention - * Stalking or following - * Or any other kinds of harassment +- **Be mindful of your language.** Any of the following behavior is unacceptable: + - Offensive comments related to gender identity and expression, sexual orientation, race, ethnicity, language, neuro-type, size, ability, class, religion, culture, subculture, political opinion, age, skill level, occupation, or background + - Threats of violence + - Deliberate intimidation + - Sexually explicit or violent material that is not contextualized and preceded by a considerate warning + - Unwelcome sexual attention + - Stalking or following + - Or any other kinds of harassment Use your best judgement. If it will possibly make others uncomfortable, do not post it. -* **Be respectful.** Disagreement is not an opportunity to attack someone else's thoughts or opinions. Although views may differ, remember to approach every situation with patience and care. -* **Be considerate.** Think about how your contribution will affect others in the community. -* **Be open minded.** Embrace new people and new ideas. Our community is continually evolving and we welcome positive change. +- **Be respectful.** Disagreement is not an opportunity to attack someone else's thoughts or opinions. Although views may differ, remember to approach every situation with patience and care. +- **Be considerate.** Think about how your contribution will affect others in the community. +- **Be open minded.** Embrace new people and new ideas. Our community is continually evolving and we welcome positive change. If you believe someone is violating the code of conduct, we ask that you report it by emailing [hello@p5js.org](mailto:hello@p5js.org). Please include your name and a description of the incident, and we will get back to you ASAP. Sometimes, participants violating the Code of Conduct are unaware that their behavior is harmful, and an open conversation clears things up to move forward. However, if a participant continues with the behavior, the p5.js team may take any action they deem appropriate, up to and including expulsion from all p5.js spaces and identification of the participant as a harasser to other p5.js members or the general public. --- + This statement is licensed under a [Creative Commons license](https://creativecommons.org/licenses/by-sa/4.0/). Please feel free to share and remix with attribution. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bcc1ebd9bf..f876fcd21d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ The GitHub issues are for bugs and feature requests for the p5.js library itself Please make sure you are posting to the correct repository. See this [section](https://github.com/processing/p5.js/blob/main/README.md#issues) for a list of all p5.js repositories. -Please be sure to review our [community statement](https://p5js.org/about/#community-statement) and [code of conduct](https://github.com/processing/p5.js/blob/main/CODE_OF_CONDUCT.md). These things are very important to us. +Please be sure to review our [community statement](https://p5js.org/about/#community-statement) and [code of conduct](https://github.com/processing/p5.js/blob/main/CODE_OF_CONDUCT.md). These things are very important to us. Check out the [contributor docs](https://p5js.org/contribute/) for more in-depth details about contributing code, bug fixes, and documentation. @@ -13,26 +13,33 @@ Check out the [contributor docs](https://p5js.org/contribute/) for more in-depth The following are some key points from our [contributor guidelines](https://p5js.org/contribute/contributor_guidelines/). Please read the full document for more details. ### Get Assigned Before Working on an Issue + You should not “jump the queue” by filing a PR for an issue that either someone else has indicated willingness to submit a contribution or has already been assigned to someone else. We will always prioritize the “first assigned, first serve” order for accepting code contributions for an issue. If you file a PR for an issue while someone else is still working on the same issue, your PR will likely be closed. ### You may follow up on Stalled Issues + If you see that it has been a few weeks since the last activity on an issue with an assigned individual, you can leave a polite comment on the issue asking for progress and if they need help with the implementation. We generally allow for people to work on their contributions at their own pace, as we understand that most people will often be working on a volunteer basis, or it simply takes more time for them to work on the feature. ### Only Issues Approved for Implementation May Be Worked On + You should not file a pull request (or start working on code changes) without a corresponding issue or before an issue has been approved for implementation, that is because there is no guarantee that the proposal will be accepted. Any pull requests filed before a proposal has been approved will be closed until approval is given to the issue. ### Include Unit Tests + Add any unit tests if you are working on adding new features or feature enhancement. Frequently run `npm test` and make sure all existing and new tests pass before submitting a PR. ### Follow Code Standards + Make sure your code follows the established code standards for p5.js. Any git commit and pull request must pass linting before it will be accepted. The easiest way for you to follow the right coding standard is to use the ESLint plugin available for your text editor with linting error highlighting (available for most popular text editors). ### Preparing Pull Requests + After making the changes you need to make, including unit tests if applicable, `npm test` does not error, and you have committed the changes, you can start preparing a pull request (PR) to get your new commits merged into the official p5.js repository. A pull request, more formally, is a request to a repo (in this case, the official p5.js repo) to pull or merge changes from another repo (in this case, your forked p5.js repo) into its commit history. ## AI Usage Policy -This project does *not* accept fully AI-generated contributions. AI tools may be used assistively only. As a contributor, you should be able to understand and take responsibility for changes you make to the codebase. + +This project does _not_ accept fully AI-generated contributions. AI tools may be used assistively only. As a contributor, you should be able to understand and take responsibility for changes you make to the codebase. Agents and AI coding assistants must follow the guidelines in `./AGENTS.md` and read the full [contributor_guidelines.mdx](https://raw.githubusercontent.com/processing/p5.js-website/main/src/content/contributor-docs/en/contributor_guidelines.mdx). -Please read the [AI Usage Policy](./AI_USAGE_POLICY.md) and [AGENTS.md](./AGENTS.md) before proceeding. \ No newline at end of file +Please read the [AI Usage Policy](./AI_USAGE_POLICY.md) and [AGENTS.md](./AGENTS.md) before proceeding. diff --git a/README.md b/README.md index d5a727fc95..40a18ee4f2 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,12 @@ p5.js is a free and open-source JavaScript library for [accessible](https://p5js ```js function setup() { -  createCanvas(400, 400); -  background(255); + createCanvas(400, 400); + background(255); } function draw() { -  circle(mouseX, mouseY, 80); + circle(mouseX, mouseY, 80); } ``` @@ -61,7 +61,8 @@ p5.js is a collaborative project with many contributors, mostly volunteers, and A quick Getting Started with the Build and setting up the repository could be found [here](https://p5js.org/contribute/contributor_guidelines/#quick-get-started-for-developers). ## AI Usage Policy -This project does *not* accept fully AI-generated contributions. AI tools may be used assistively only. As a contributor, you should be able to understand and take responsibility for changes you make to the codebase. + +This project does _not_ accept fully AI-generated contributions. AI tools may be used assistively only. As a contributor, you should be able to understand and take responsibility for changes you make to the codebase. More details can be found in our [AI Usage Policy](./AI_USAGE_POLICY.md) and [AGENTS.md](./AGENTS.md). @@ -74,35 +75,39 @@ Anyone interested can volunteer to be a steward! There are no specific requireme p5.js was created by [Lauren Lee McCarthy](https://github.com/lmccart) in 2013 as a new interpretation of Processing for the context of the web. Since then we have allowed ourselves space to deviate and grow, while drawing inspiration from Processing and our shared community. p5.js is sustained by a community of contributors, with support from the Processing Foundation. p5.js follows a rotating leadership model started in 2020. Learn more about the [people](https://p5js.org/people/) behind p5.js. Current Lead/Mentor -* [@ksen0](https://github.com/ksen0) - p5.js Lead,2024-present -* [@limzykenneth](https://github.com/limzykenneth) - p5.js Mentor,2023-present + +- [@ksen0](https://github.com/ksen0) - p5.js Lead,2024-present +- [@limzykenneth](https://github.com/limzykenneth) - p5.js Mentor,2023-present Lead/Mentor Alumni -* [@lmccart](https://github.com/lmccart) - p5.js Creator -* [@qianqianye](https://github.com/qianqianye) - p5.js Lead,2021-2025 -* [@outofambit](https://github.com/outofambit) - p5.js Co-Lead 2021-22, Mentor 2022-2023 -* [@mcturner1995](https://github.com/mcturner1995) - p5.js Lead 2020 + +- [@lmccart](https://github.com/lmccart) - p5.js Creator +- [@qianqianye](https://github.com/qianqianye) - p5.js Lead,2021-2025 +- [@outofambit](https://github.com/outofambit) - p5.js Co-Lead 2021-22, Mentor 2022-2023 +- [@mcturner1995](https://github.com/mcturner1995) - p5.js Lead 2020 -| Area | Steward(s) | -|------|-------------| -| Maintainers | [@davepagurek](https://github.com/davepagurek), [@doradocodes](https://github.com/doradocodes), [@ksen0](https://github.com/ksen0), [@limzykenneth](https://github.com/limzykenneth), [@perminder-17](https://github.com/perminder-17) | -| Accessibility | [@calebfoss](https://github.com/calebfoss) | -| Accessibility (p5.js-website) | [@coseeian](https://github.com/coseeian) | -| Color | [@limzykenneth](https://github.com/limzykenneth) | -| Core | [@davepagurek](https://github.com/davepagurek) | -| DevOps | [@Vaivaswat2244](https://github.com/Vaivaswat2244), [@limzykenneth](https://github.com/limzykenneth), [@lirenjie95](https://github.com/lirenjie95) | -| Documentation | [@limzykenneth](https://github.com/limzykenneth), [@perminder-17](https://github.com/perminder-17) | -| Graphics (WebGL) | [@RandomGamingDev](https://github.com/RandomGamingDev), [@davepagurek](https://github.com/davepagurek), [@perminder-17](https://github.com/perminder-17) | -| Graphics (WebGPU) | [@aashu2006](https://github.com/aashu2006), [@davepagurek](https://github.com/davepagurek), [@perminder-17](https://github.com/perminder-17) | -| i18n (es) | [@marioguzzzman](https://github.com/marioguzzzman) | -| i18n (hi) | [@Divyansh013](https://github.com/Divyansh013) | -| i18n (ko) | [@eupthere](https://github.com/eupthere) | -| i18n (zh) | [@limzykenneth](https://github.com/limzykenneth), [@lirenjie95](https://github.com/lirenjie95) | -| p5.js-web-editor | [@kitlord](https://github.com/kitlord) | -| p5.js-website | [@Nwakaego-Ego](https://github.com/Nwakaego-Ego), [@clairep94](https://github.com/clairep94), [@doradocodes](https://github.com/doradocodes), [@ksen0](https://github.com/ksen0) | -| p5.sound.js | [@ogbabydiesal](https://github.com/ogbabydiesal) | -| Typography | [@dhowe](https://github.com/dhowe) | + +| Area | Steward(s) | +| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Maintainers | [@davepagurek](https://github.com/davepagurek), [@doradocodes](https://github.com/doradocodes), [@ksen0](https://github.com/ksen0), [@limzykenneth](https://github.com/limzykenneth), [@perminder-17](https://github.com/perminder-17) | +| Accessibility | [@calebfoss](https://github.com/calebfoss) | +| Accessibility (p5.js-website) | [@coseeian](https://github.com/coseeian) | +| Color | [@limzykenneth](https://github.com/limzykenneth) | +| Core | [@davepagurek](https://github.com/davepagurek) | +| DevOps | [@Vaivaswat2244](https://github.com/Vaivaswat2244), [@limzykenneth](https://github.com/limzykenneth), [@lirenjie95](https://github.com/lirenjie95) | +| Documentation | [@limzykenneth](https://github.com/limzykenneth), [@perminder-17](https://github.com/perminder-17) | +| Graphics (WebGL) | [@RandomGamingDev](https://github.com/RandomGamingDev), [@davepagurek](https://github.com/davepagurek), [@perminder-17](https://github.com/perminder-17) | +| Graphics (WebGPU) | [@aashu2006](https://github.com/aashu2006), [@davepagurek](https://github.com/davepagurek), [@perminder-17](https://github.com/perminder-17) | +| i18n (es) | [@marioguzzzman](https://github.com/marioguzzzman) | +| i18n (hi) | [@Divyansh013](https://github.com/Divyansh013) | +| i18n (ko) | [@eupthere](https://github.com/eupthere) | +| i18n (zh) | [@limzykenneth](https://github.com/limzykenneth), [@lirenjie95](https://github.com/lirenjie95) | +| p5.js-web-editor | [@kitlord](https://github.com/kitlord) | +| p5.js-website | [@Nwakaego-Ego](https://github.com/Nwakaego-Ego), [@clairep94](https://github.com/clairep94), [@doradocodes](https://github.com/doradocodes), [@ksen0](https://github.com/ksen0) | +| p5.sound.js | [@ogbabydiesal](https://github.com/ogbabydiesal) | +| Typography | [@dhowe](https://github.com/dhowe) | + ## Contributors diff --git a/SECURITY.md b/SECURITY.md index d8d4bb5fcb..b264a2624b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,11 +5,13 @@ Please report security vulnerabilities by emailing: responsible.disclosure@processingfoundation.org ## What to Expect + - We will acknowledge your email within 72 hours - We will provide regular updates about our progress - Once the issue is confirmed and fixed, we may ask you to verify the solution ## Disclosure Policy + - Please do not disclose the vulnerability publicly until we have had a chance to address it - We do not offer bounties as we are a non-profit organization - We appreciate your efforts to responsibly disclose your findings diff --git a/contributor_docs/README.md b/contributor_docs/README.md index f48328af37..603e8d9e07 100644 --- a/contributor_docs/README.md +++ b/contributor_docs/README.md @@ -3,12 +3,15 @@ Thanks for your interest in contributing to p5.js! p5.js is a collaborative project with contributions from many volunteers. Our community is always looking for contributors and appreciates involvement in all forms. We acknowledge that not everyone has the capacity, time, or financial means to participate actively or in the same ways. We want to expand the meaning of the word “contributor.” Whether you're an experienced developer or just starting out, we value your involvement. Your unique perspectives, skills, and experiences enrich our community, and we encourage you to get involved in a way that works for you. It includes documentation, teaching, writing code, making art, writing, design, activism, organizing, curating, or anything else you might imagine. Our [contribute page](https://p5js.org/contribute/) gives an overview of different ways to get involved and contribute. p5.js project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. We use the @all-contributors bot to handle adding people to the README.md file. You can ask @all-contributors bot to add you in an issue or PR comment like so: + ``` @all-contributors please add @[your GitHub handle] for [your contribution type] ``` + You can find relevant contribution type [here](https://allcontributors.org/docs/en/emoji-key). Although we will usually automatically add you to the contributor list using the bot after merging your PR. The contributor docs are published on p5.js [website](https://p5js.org/contribute/), and hosted on p5.js [GitHub repository](https://github.com/processing/p5.js/tree/main/contributor_docs). # Before Contributing + Contributing to p5.js should be a stress free experience and we welcome contributions of all levels, whether you are just fixing a small typo in the documentation or refactoring complex 3D rendering functionalities. However there are just a few things you should be familiar with before starting your contribution. First, please have a read through our [community statement](https://p5js.org/about/#community-statement). @@ -16,14 +19,18 @@ First, please have a read through our [community statement](https://p5js.org/abo Next, we are currently prioritizing work that expands access (inclusion and accessibility) to p5.js! See [our access statement](./access.md) for more details. # Get Started + Now you are ready to start contributing to p5.js! There are many ways to get started with contributing to p5.js and many reasons to do so. For the purpose of this documentation, we will split contributions roughly into two categories. + - Source code contribution (including documentation) - Non-source code contribution Depending on what kind of contribution you are making to p5.js, please read on to the relevant section of this documentation. ## [Source code contribution](./contributor_guidelines.md) + For a typical contribution to the p5.js or p5.js-website repository, we will follow the following steps: + 1. Open an issue 2. Discuss 3. Approved for opening a Pull Request (PR) @@ -39,6 +46,7 @@ Most of the time we will stick with this workflow quite strictly and, especially **We see contributing to p5.js as a learning opportunity** and we don't measure success by only looking at the volume of contributions we received. There is no time limit on how long it takes you to complete a contribution, so take your time and work at your own pace (we may check in after a long period of inactivity). Ask for help from any of the stewards or maintainers if you need them and we'll try our best to support you. For information related to area stewards or general maintenance of p5.js GitHub repository, please check out the [steward guidelines](./steward_guidelines.md). ## Non-source code contribution + There are many more ways to contribute to p5.js through non-source code contribution than can be exhaustively listed here. Some of the ways may also involve working with some of the p5.js repositories (such as adding examples, writing tutorials for the website, etc.). Depending on what the planned contribution is, we may be able to support you in different ways so do reach out to us via any channel available to you (email, social media, [Discourse forum](https://discourse.processing.org/c/p5js/10), Discord, etc). Here are just some ways you can contribute: **Create.** Inspire others with your sketches. p5.js is looking for designers, artists, coders, and programmers to showcase their creative, amazing work on the community sketch gallery. Don’t forget to tag @p5xjs on [Instagram](https://www.instagram.com/p5xjs/) and [X](https://twitter.com/p5xjs/), and we will do our best to share what you're doing. diff --git a/contributor_docs/access.md b/contributor_docs/access.md index 7fcac44946..2835612454 100644 --- a/contributor_docs/access.md +++ b/contributor_docs/access.md @@ -11,7 +11,7 @@ Increasing access is not focused on expanding the raw number of people in the p5 Access here means making p5.js equitable for: - People who speak languages other than English -- Black, Indigenous, People of Color, and people of marginalized ethnicities +- Black, Indigenous, People of Color, and people of marginalized ethnicities - Lesbian, gay, bisexual, queer, questioning, pansexual, and asexual people - Trans, genderfluid, agender, intersex, and two-spirit people, women, and others with marginalized genders - People who are blind, d/Deaf[^2] or hard of hearing, disabled/have a disability, neurodivergent, and chronically ill[^3] @@ -26,8 +26,8 @@ Access here means making p5.js equitable for: We recognize the complexity of the terms used to describe our respective identities. Language is nuanced, evolving, and contested. This is not an exhaustive list. We provide an attempt to name and be accountable to our commitments and to the diverse needs of the p5.js community. - ### Examples + These are examples of efforts we believe increase access: - Translating documentation and other materials into more languages, decentering linguistic imperialism[^4] (e.g., Rolando Vargas’ [Processing in Kuna Language](https://medium.com/@ProcessingOrg/culture-as-translation-processing-in-kuna-language-with-rolando-vargas-and-edinson-izquierdo-8079f14851f7), Felipe Santos Gomes, Julia Brasil, Katherine Finn Zander, and Marcela Mancino’s [Pê Cinco: Internationalization and Popularization for Portuguese Speakers](https://medium.com/processing-foundation/translating-p5-js-into-portuguese-for-the-brazilian-community-14b969e77ab1)) @@ -39,20 +39,22 @@ These are examples of efforts we believe increase access: - Supporting the creation of educational resources (e.g., Adekemi Sijuwade-Ukadike’s [A11y Syllabus](http://a11ysyllabus.site/)) - Publishing documentation and reports of our work that follow WCAG guidelines, use plain language, and focus on beginners from diverse experiences (e.g., [OSACC p5.js Access Report](https://github.com/processing/OSACC-p5.js-Access-Report)) - - ## Maintenance + We are not accepting feature requests that do not support our effort to increase access. You'll see this criteria reflected in our issue and pull request templates. We also affirm our intention to maintain the existing feature set of p5.js. We'd like to fix bugs regardless of which area of the codebase they're in. We believe consistency of the tool makes it more accessible for beginners. Examples of feature requests that improve accessibility include: Performance increases for people using less powerful hardware (e.g., Support for drawing to/reading from framebuffers) Consistency in the API (e.g, Add arcVertex() for creating arcs with beginShape()/endShape()) -___ +--- Please consider this a ‘living document.' We will continue the conversation about what it means to prioritize access. We invite our community to engage with this document and the values it describes. If you have ideas or suggestions, we invite you to share them as an issue on Github or by emailing hello@p5js.org. -This version of the p5.js Access Statement was revised in collaboration with Evelyn Masso, Nat Decker, Bobby Joe Smith III, Sammie Veeler, Sonia (Suhyun) Choi, Xin Xin, Kate Hollenbach, Lauren Lee McCarthy, Caroline Sinders, Qianqian Ye, Tristan Jovani Magno Espinoza, Tanvi Sharma, Tsige Tafesse, and Sarah Ciston at the 2023 Open Source Arts Contributors Conference. It was finalized and published by Bobby Joe Smith III and Nat Decker through the support of the Processing Foundation Fellowship. +This version of the p5.js Access Statement was revised in collaboration with Evelyn Masso, Nat Decker, Bobby Joe Smith III, Sammie Veeler, Sonia (Suhyun) Choi, Xin Xin, Kate Hollenbach, Lauren Lee McCarthy, Caroline Sinders, Qianqian Ye, Tristan Jovani Magno Espinoza, Tanvi Sharma, Tsige Tafesse, and Sarah Ciston at the 2023 Open Source Arts Contributors Conference. It was finalized and published by Bobby Joe Smith III and Nat Decker through the support of the Processing Foundation Fellowship. [^1]: Crenshaw, Kimberlé (1989). "Demarginalizing the intersection of race and sex: a black feminist critique of antidiscrimination doctrine, feminist theory and antiracist politics". University of Chicago Legal Forum. 1989 (1): 139–167. ISSN 0892-5593. Full text at Archive.org. -[^2]: Capital ‘D’ Deaf refers to people who are culturally Deaf or part of the Deaf community while lower case ‘d’ deaf is an audiological term that can describe people not associated with Deaf identity. + +[^2]: Capital ‘D’ Deaf refers to people who are culturally Deaf or part of the Deaf community while lower case ‘d’ deaf is an audiological term that can describe people not associated with Deaf identity. + [^3]: There are differing preferences between ‘person-first’ vs. ‘identity-first’ language within the disability community. Read [Unpacking the debate over person-first vs. identity-first language in the autism community](https://news.northeastern.edu/2018/07/12/unpacking-the-debate-over-person-first-vs-identity-first-language-in-the-autism-community/), [Disability-Affirming Language: Person-First versus Identity-First Language](https://editorstorontoblog.com/2024/02/23/disability-affirming-language-person-first-versus-identity-first-language/), and [Person-First and Identity-First Language Glossary](https://ogs.ny.gov/system/files/documents/2024/02/person-first-and-identity-first-glossary_english_final.pdf). -[^4]: Linguistic Imperialism, or Language Imperialism, refers to the ongoing domination/prioritization/imposition of certain languages such as English at the expense of native languages due to imperial expansion and globalization. + +[^4]: Linguistic Imperialism, or Language Imperialism, refers to the ongoing domination/prioritization/imposition of certain languages such as English at the expense of native languages due to imperial expansion and globalization. diff --git a/contributor_docs/ar/README.md b/contributor_docs/ar/README.md index 7379dfd971..3381ad6fac 100644 --- a/contributor_docs/ar/README.md +++ b/contributor_docs/ar/README.md @@ -1,18 +1,22 @@ # 🌸مرحبا 🌺 -شكرا لإهتمامك بالمساهمة في p5.js! مجتمعنا يقدر المساهمات بكل أشكالها ويسعى إلى توسيع معنى كلمة "مساهم/ة" بأقصى شكل ممكن؛ بشكل يتضمن توثيق البرمجيات، التدريس، كتابة الشيفرة البرمجية، صناعة الفن، الكتابة، التصميم، النشاطية، التنظيم، نَظْم المحتوى أو أي شيء أخر يمكن أن تتخيله. [صفحة المجتمع خاصتنا](https://p5js.org/community/#contribute) تعطي ملخصا للطرق المختلفة التي يمكن بها المشاركة والمساهمة معنا. -هذا المشروع يتبع المواصفات القياسية لمشروع [all-contributors "كل المساهمين"](https://github.com/kentcdodds/all-contributors). نحن نستعمل البوت @all-contributors كي نضيف المساهمين/ات إلى الملف README.md. يمكنك الطلب من البوت @all-contributors أن يضيفك عن طريق كتابة الآتي في تعليق على طلب مشكلة "issue" على جيت هب "GitHub" أو طلب سحب"pull request" : +شكرا لإهتمامك بالمساهمة في p5.js! مجتمعنا يقدر المساهمات بكل أشكالها ويسعى إلى توسيع معنى كلمة "مساهم/ة" بأقصى شكل ممكن؛ بشكل يتضمن توثيق البرمجيات، التدريس، كتابة الشيفرة البرمجية، صناعة الفن، الكتابة، التصميم، النشاطية، التنظيم، نَظْم المحتوى أو أي شيء أخر يمكن أن تتخيله. [صفحة المجتمع خاصتنا](https://p5js.org/community/#contribute) تعطي ملخصا للطرق المختلفة التي يمكن بها المشاركة والمساهمة معنا. + +هذا المشروع يتبع المواصفات القياسية لمشروع [all-contributors "كل المساهمين"](https://github.com/kentcdodds/all-contributors). نحن نستعمل البوت @all-contributors كي نضيف المساهمين/ات إلى الملف README.md. يمكنك الطلب من البوت @all-contributors أن يضيفك عن طريق كتابة الآتي في تعليق على طلب مشكلة "issue" على جيت هب "GitHub" أو طلب سحب"pull request" : + ``` @all-contributors please add @[اسم المسخدم خاصتك على جيت هب] for [نوع مساهمتك] ``` + يمكنك معرفة نوع مساهمتك المناسب من [هنا](https://allcontributors.org/docs/en/emoji-key)، على الرغم من أننا غالبا سنضيفك بشكل تلقائي إلى قائمة المساهمين بعد قبول طلب السحب خاصتك. دليل المساهمين منشور على [موقع](https://p5js.org/contributor-docs/#/) p5.js ومُستضاف على[ مستودع "repository" جيت هب](https://github.com/processing/p5.js/tree/main/contributor_docs) الخاص بـ p5.js. # قبل المساهمة معنا -المساهمة في مشروع p5.js يجب أن تكون تجربة خالية من أي ضغط، فنحن نرحب بكل المساهمات بغض النظر عن حجمها، سواء كانت إصلاح خطأ مطبعي بسيط أو إعادة هيكلة الآليات المعقدة لعرض الرسوم ثلاثية الأبعاد، لكن توجد بعض الأشياء التي يجب معرفتها وفهمها قبل البدء في المساهمة. + +المساهمة في مشروع p5.js يجب أن تكون تجربة خالية من أي ضغط، فنحن نرحب بكل المساهمات بغض النظر عن حجمها، سواء كانت إصلاح خطأ مطبعي بسيط أو إعادة هيكلة الآليات المعقدة لعرض الرسوم ثلاثية الأبعاد، لكن توجد بعض الأشياء التي يجب معرفتها وفهمها قبل البدء في المساهمة. بدايةً: من فضلك قم/قومي بقراءة [بيان مجتمعنا](https://p5js.org/community/) -أما بعد: نحن حاليا نعطي الأولوية للأعمال التي تُزيد الوصول (إمكانية الوصول والإتاحة وحقوق المعاقين/ات) لـ p5.js! للمزيد من التفاصيل، يمكنك إلقاء نظرة على [بيان الإتاحة خاصتنا](./access.md) +أما بعد: نحن حاليا نعطي الأولوية للأعمال التي تُزيد الوصول (إمكانية الوصول والإتاحة وحقوق المعاقين/ات) لـ p5.js! للمزيد من التفاصيل، يمكنك إلقاء نظرة على [بيان الإتاحة خاصتنا](./access.md) # البدء @@ -26,7 +30,8 @@ على حسب نوع المساهمة المراد القيام بها إلى p5.js، يمكنك قراءة الجزء المناسب من هذا الدليل. ## [المساهمات إلى الشيفرة المصدرية](./contributor_guidelines.md) -للقيام بمساهمة مثالية إلى مستودع p5.js أو موقع p5.js، يجب إتباع الخطوات الآتية: + +للقيام بمساهمة مثالية إلى مستودع p5.js أو موقع p5.js، يجب إتباع الخطوات الآتية:
  1. عمل طلب مشكلة على جيت هب
  2. مناقشة طلب المشكلة
  3. @@ -44,9 +49,9 @@ **نحن نرى المساهمة إلى p5.js كفرصة للتعلم** ولا نقيس النجاح بعدد المساهمات التي تصلنا. لا توجد مهلة تَحدُك لتنفيذ مساهمتك، لذا يمكنك اتخاذ الوقت والسرعة المناسبة لك (قد نراسلك إذا غبت لفترة طويلة). يمكنك سؤال المتعهدين/ات أو المشرفين/ات إذا احتجتهم وسوف نبذل قصارى جهدنا حتى نساعدك. ## المساهمات إلى غير الشيفرة المصدرية -هناك العديد من الطرق للمساهمة إلى p5.js بعيدا عن الشيفرة المصدرية، أكثر حتى من أن نحصيها هنا، بعض الطرق قد تتضمن العمل مع مستودع p5.js (كإضافة الأمثلة، أو كتابة درس للموقع، إلخ). على حسب المساهمة المخطط لها، قد تكون لدينا القدرة على مساعدتك بطرق عدة، لذا يمكنك التواصل معنا عبر أي قناة من قنوات التواصل المتاحة لك (البريد الإلكتروني، وسائل التواصل الإجتماعي، منتدى النقاش، ديسكورد، إلخ). + +هناك العديد من الطرق للمساهمة إلى p5.js بعيدا عن الشيفرة المصدرية، أكثر حتى من أن نحصيها هنا، بعض الطرق قد تتضمن العمل مع مستودع p5.js (كإضافة الأمثلة، أو كتابة درس للموقع، إلخ). على حسب المساهمة المخطط لها، قد تكون لدينا القدرة على مساعدتك بطرق عدة، لذا يمكنك التواصل معنا عبر أي قناة من قنوات التواصل المتاحة لك (البريد الإلكتروني، وسائل التواصل الإجتماعي، منتدى النقاش، ديسكورد، إلخ). ## [المتعهدين/ات والمشرفين/ات](./steward_guidelines.md) للدليل الخاص بالمتعهدين/ات أو الصيانة العامة لمستودع p5.js على جيت هب، يمكنك إلقاء نظرة على [دليل المتعهدين](./steward_guidelines.md) - diff --git a/contributor_docs/archive/benchmarking_p5.md b/contributor_docs/archive/benchmarking_p5.md index 71522071ad..067d30f191 100644 --- a/contributor_docs/archive/benchmarking_p5.md +++ b/contributor_docs/archive/benchmarking_p5.md @@ -1,3 +1,3 @@ # Benchmarking p5.js -Benchmarking has been moved to its own repo available at https://github.com/limzykenneth/p5-benchmark. A overall view of the benchmark result of the latest version of p5.js can be seen at https://limzykenneth.github.io/p5-benchmark/. This is still a work in progress. \ No newline at end of file +Benchmarking has been moved to its own repo available at https://github.com/limzykenneth/p5-benchmark. A overall view of the benchmark result of the latest version of p5.js can be seen at https://limzykenneth.github.io/p5-benchmark/. This is still a work in progress. diff --git a/contributor_docs/archive/es6-adoption.md b/contributor_docs/archive/es6-adoption.md index 76b5ca3e4a..34f29cdbba 100644 --- a/contributor_docs/archive/es6-adoption.md +++ b/contributor_docs/archive/es6-adoption.md @@ -1,11 +1,13 @@ ## ES6 Adoption -p5.js has recently adopted the ECMAScript 2015 (ES6) language specifications in order to reduce the complexity of the codebase, increase readability, and utilize features that facilitate elegant and effective coding practices for both newcomers and seasoned contributors. + +p5.js has recently adopted the ECMAScript 2015 (ES6) language specifications in order to reduce the complexity of the codebase, increase readability, and utilize features that facilitate elegant and effective coding practices for both newcomers and seasoned contributors. The transition process was initially [discussed](https://github.com/processing/p5.js/issues/3758) with the aim of migrating p5 to ES6, which later led to a series of widespread syntactical changes in the codebase aligned with ES6 specifications. More info on the initial transition can be found [here](https://github.com/processing/p5.js/pull/3874). These changes consisted of minor modifications to the build system to facilitate processing, linting and testing the library based on ES6 standards, as well as major and ubiquitous syntactical modification in line with ES6 features. It is worthy to note that as of writing this, these transformations are by no means complete, and do not reflect nor implement every possible feature of ES6. They are intended to facilitate a smoother transition to properly and efficiently utilize ES6 features if and when aligned with the community interests and standards. And serve to motivate contributors to gradually conform to the new style and features. As such, we encourage all contributors to adhere to ES6 standards in their commits and pull requests as long as the utilized features: + 1. Reduce syntactical ambiguity and idiosyncrasies associated with Javascript 2. Increase readability and code clarity 3. Refrain from code obfuscation and over-abstraction (to aid with readability) @@ -13,7 +15,8 @@ As such, we encourage all contributors to adhere to ES6 standards in their commi 5. Ease contribution and development 6. Do not sacrifice performance -There are many ES6 features that have not been utilized in p5 as of yet. However many areas of the library could benefit from such features which include but are not limited to: +There are many ES6 features that have not been utilized in p5 as of yet. However many areas of the library could benefit from such features which include but are not limited to: + - Generators - Number Type Checking - Set/Map Data-Structures @@ -23,6 +26,7 @@ There are many ES6 features that have not been utilized in p5 as of yet. However It is worth noting that full compliance with ES6 standards demands the efforts of the community in all areas of p5 and due to its nature is a gradual process. ### Relevant Resources + - [Official ES6 Language Specification](https://www.ecma-international.org/ecma-262/6.0/) - [ES6 Performance Comparison](http://incaseofstairs.com/six-speed/) - [ES6 Features: Overview & Comparison](http://es6-features.org/) @@ -30,24 +34,25 @@ It is worth noting that full compliance with ES6 standards demands the efforts o - [Exploring ES6 (Book)](https://exploringjs.com/es6/) ### Performance -Although our goal is to comply and adhere to ES6 standards, it is important to note that p5 relies on performant code. As of writing this, many of the ES6 features are associated with performance-hits, bottlenecks, and overheads. Thus, it is important to refrain from using ES6 features that result in poor performing code. However, this seems to be a temporary concern given that browser engine developers are actively engaged in improving the performance of these new features. However, for the time being, it is important to prioritize performance over full compliance with ES6 standards, especially when syntactical changes do not result in major improvements aligned with the community goals. + +Although our goal is to comply and adhere to ES6 standards, it is important to note that p5 relies on performant code. As of writing this, many of the ES6 features are associated with performance-hits, bottlenecks, and overheads. Thus, it is important to refrain from using ES6 features that result in poor performing code. However, this seems to be a temporary concern given that browser engine developers are actively engaged in improving the performance of these new features. However, for the time being, it is important to prioritize performance over full compliance with ES6 standards, especially when syntactical changes do not result in major improvements aligned with the community goals. For a comparison of ES6 features' performance please refer to [this link](http://incaseofstairs.com/six-speed/). For a discussion that highlights performance prioritization please have a look at the case of `forEach` [here](https://github.com/processing/p5.js/issues/3758#issuecomment-507922753). - ### Browser Compatibility and Transpiling -p5 is built against [browserlist's](https://github.com/browserslist/browserslist) `last 2 versions` and `not dead`, in order to support the latest features while maintaining accessibility to a large userbase. To check supported browsers please visit [this link](https://browserl.ist/?q=last+2+versions). Moreover, as of now, p5 is [transpiled](https://en.wikipedia.org/wiki/Source-to-source_compiler) to the earlier ES5 standards using [Babel](https://babeljs.io/). This means that although we use ES6 or possibly more cutting edge features of Javascript, all these features are eventually converted to the earlier ES5 standards when the library is built for production (`p5-min.js`). This is to ensure compatibility with older browsers and mobile devices that fail to support these new features, such as Internet Explorer. So while it is important to be cognizant of browser compatibility, it is worth noting that ES6 features are eventually converted to the widely supported ES5 standards, and for the most part feature compatibility can be ignored thanks to the transpilation process. +p5 is built against [browserlist's](https://github.com/browserslist/browserslist) `last 2 versions` and `not dead`, in order to support the latest features while maintaining accessibility to a large userbase. To check supported browsers please visit [this link](https://browserl.ist/?q=last+2+versions). Moreover, as of now, p5 is [transpiled](https://en.wikipedia.org/wiki/Source-to-source_compiler) to the earlier ES5 standards using [Babel](https://babeljs.io/). This means that although we use ES6 or possibly more cutting edge features of Javascript, all these features are eventually converted to the earlier ES5 standards when the library is built for production (`p5-min.js`). This is to ensure compatibility with older browsers and mobile devices that fail to support these new features, such as Internet Explorer. So while it is important to be cognizant of browser compatibility, it is worth noting that ES6 features are eventually converted to the widely supported ES5 standards, and for the most part feature compatibility can be ignored thanks to the transpilation process. -While reading this you might wonder: "*if ES6 is converted to ES5 what is the point of using these features?*" The answer to this very appropriate question is that ES5 will undoubtedly be replaced in the upcoming years by ES6. And perhaps when the time comes, there won't be a need to transpile the library back to ES5. Using ES6 standards primarily serves to simplify the code, increase readability and lower the barrier of entry for new developers while ensuring that p5 benefits from new features. Most importantly it makes the codebase future-proof. +While reading this you might wonder: "_if ES6 is converted to ES5 what is the point of using these features?_" The answer to this very appropriate question is that ES5 will undoubtedly be replaced in the upcoming years by ES6. And perhaps when the time comes, there won't be a need to transpile the library back to ES5. Using ES6 standards primarily serves to simplify the code, increase readability and lower the barrier of entry for new developers while ensuring that p5 benefits from new features. Most importantly it makes the codebase future-proof. ### Compatibility vs. Performance -While for the most part browser compatibility can be ignored, the same is not true for performance. Conversion of ES6 to ES5 can be safely completed, but for most cases, there are performance penalties associated with the generated ES5 code. Again please refer to [this link](http://incaseofstairs.com/six-speed/) to see how code transpiled by Babel performs versus native ES5 implementations. +While for the most part browser compatibility can be ignored, the same is not true for performance. Conversion of ES6 to ES5 can be safely completed, but for most cases, there are performance penalties associated with the generated ES5 code. Again please refer to [this link](http://incaseofstairs.com/six-speed/) to see how code transpiled by Babel performs versus native ES5 implementations. ### ES6 Coding Guidelines + - Abandoning `require` in favor of ES6 `import` [[Read More](https://exploringjs.com/es6/ch_modules.html#sec_importing-exporting-details)] - Abandoning `module.exports` in favor of ES6 `export` [[Read More](https://exploringjs.com/es6/ch_modules.html#sec_importing-exporting-details)] - **Exception:** `app.js` still using `module.exports = p5;` due to build system limitations @@ -60,8 +65,8 @@ While for the most part browser compatibility can be ignored, the same is not tr - **Incorrect:** `p5.prototype.myMethod = function() {...}.bind(this);` - Constants are imported such that syntax replicates old format: `constants.TWO_PI` - ### Summary of Transformations + - [Modules: Export/Import](http://es6-features.org/#ValueExportImport) - [Modules: Default & Wildcard](http://es6-features.org/#ValueExportImport) - [Constants](http://es6-features.org/#Constants) @@ -83,5 +88,6 @@ While for the most part browser compatibility can be ignored, the same is not tr **Note:** Please Update the list above as new features are incorporated into the library. - ### Current Issues +### Current Issues + - [`#3883`](https://github.com/processing/p5.js/issues/3883): Failure to construct `new p5()` when using `combineModules` to create custom bundles. Global mode is unaffected and behaves as expected. diff --git a/contributor_docs/archive/internationalization.md b/contributor_docs/archive/internationalization.md index 4d75cb124e..42f04371c0 100644 --- a/contributor_docs/archive/internationalization.md +++ b/contributor_docs/archive/internationalization.md @@ -35,13 +35,13 @@ import { translator } from './internationalization'; Without internationalization you might log a message with the text inline. ```js -console.log('Loading your sketch right now!') +console.log('Loading your sketch right now!'); ``` Instead, you use `translator`: ```js -console.log(translator('sketch.loading')) +console.log(translator('sketch.loading')); ``` This tells the translator to get the "`sketch.loading`" message in whatever language we've detected that the user prefers. @@ -57,7 +57,7 @@ console.log('I couldnt find ' + file.name + '. Are you sure it's there?') would become something like ```js -console.log(translator('fileLoading.notFound', { fileName: file.name })) +console.log(translator('fileLoading.notFound', { fileName: file.name })); ``` Translations like this expect the variables it uses to have a certain name, so make sure you use that. Check a translation file (look in `translations/{YOUR_LANGUAGE}/`) to see what the variable name is. You'll find the translation under the object path in the translation key. @@ -87,7 +87,8 @@ This will generate you a fresh translations file in `translations/{LANGUAGE_CODE You'll also need to add an entry for it in [`translations/index.js`](../translations/index.js) and [`translations/dev.js`](../translations/dev.js). You can follow the pattern used in that file for `en` and `es`. ### Testing changes -The bulk of translations are not included in the final library, but are hosted online and are automatically downloaded by p5.js when it needs them. Updates to these only happen whenever a new version of p5.js is released. + +The bulk of translations are not included in the final library, but are hosted online and are automatically downloaded by p5.js when it needs them. Updates to these only happen whenever a new version of p5.js is released. However, if you want to see your changes (or any other changes which aren't released yet), you can simply run `npm run dev` which will build p5.js configured to use the translation files present locally on your computer, instead of the ones on the internet. diff --git a/contributor_docs/archive/issue_labels.md b/contributor_docs/archive/issue_labels.md index d70bb5fc33..ec73b4b289 100644 --- a/contributor_docs/archive/issue_labels.md +++ b/contributor_docs/archive/issue_labels.md @@ -4,7 +4,7 @@ p5.js uses a set of labels to help sort and organize issues. -All issues should have at least two labels to indicate the status and which areas are affected. +All issues should have at least two labels to indicate the status and which areas are affected. ## Status @@ -22,28 +22,29 @@ All issues should have at least two labels to indicate the status and which area | More Info Needed | Require more info to illustrate the issue | | Please Help Label | Not sure which label should be added | - ## Areas + Indicate the part of the code base affected by the issue. -All the Area labels are now coordinated with the [src folder](https://github.com/processing/p5.js/tree/main/src) structure. -* Area:Accessibility -* Area:Color -* Area:Core -* Area:Data -* Area:DOM -* Area:Events -* Area:Image -* Area:IO -* Area:Math -* Area:Typography -* Area:Utilities -* Area:WebGL +All the Area labels are now coordinated with the [src folder](https://github.com/processing/p5.js/tree/main/src) structure. + +- Area:Accessibility +- Area:Color +- Area:Core +- Area:Data +- Area:DOM +- Area:Events +- Area:Image +- Area:IO +- Area:Math +- Area:Typography +- Area:Utilities +- Area:WebGL All the labels below are coordinated with the rest of the steward [focus areas](https://github.com/processing/p5.js#stewards). -* Build Process -* Unit Testing -* Internalization -* Friendly Errors -* Documentation +- Build Process +- Unit Testing +- Internalization +- Friendly Errors +- Documentation diff --git a/contributor_docs/archive/roadmap.md b/contributor_docs/archive/roadmap.md index e0c5a9cf24..adb49c853f 100644 --- a/contributor_docs/archive/roadmap.md +++ b/contributor_docs/archive/roadmap.md @@ -5,62 +5,68 @@ This document is a list of possible features, improvements, and maintenance item With the exception of ES6 migration, the tasks here are not ordered by priority. The [milestones](https://github.com/processing/p5.js/milestones) on github is where we prioritize tasks for releases and track progress. ## ES6 migration -* Phase 1: User facing - * p5js.org tutorials (issue per tutorial) - * p5js.org examples (issue per section) - * p5js.org reference examples (issue per section) -* Phase 2: Developer facing - * Build processes - * Developer docs -* Phase 3: Codebase - * TBD: This may be done by one dedicated person, we will not start on this until this is determined. -* Later phase: - * Other materials -- To what extent do external materials (Getting Started with p5.js book, Coding Train) need to be updated to ensure a smooth rollout of educational resources? - * Contributed libraries + +- Phase 1: User facing + - p5js.org tutorials (issue per tutorial) + - p5js.org examples (issue per section) + - p5js.org reference examples (issue per section) +- Phase 2: Developer facing + - Build processes + - Developer docs +- Phase 3: Codebase + - TBD: This may be done by one dedicated person, we will not start on this until this is determined. +- Later phase: + - Other materials -- To what extent do external materials (Getting Started with p5.js book, Coding Train) need to be updated to ensure a smooth rollout of educational resources? + - Contributed libraries ## Maintenance -* Audio updates for pending Chrome changes (user must interact first) - * Update reference documentation and examples, and examples page - * Update FES to warn about this? -* More complete unit tests - * Clearer tutorial for doing this -* Improve Friendly Error System documentation - * Add comments to https://github.com/processing/p5.js/blob/main/src/core/friendly_errors/fes_core.js to better document what’s happening in this process. + +- Audio updates for pending Chrome changes (user must interact first) + - Update reference documentation and examples, and examples page + - Update FES to warn about this? +- More complete unit tests + - Clearer tutorial for doing this +- Improve Friendly Error System documentation + - Add comments to https://github.com/processing/p5.js/blob/main/src/core/friendly_errors/fes_core.js to better document what’s happening in this process. ## Bug fixing -* Mobile device support / upgrades -* WebGL robustness - * Lighting - * lights() implementation - * Per pixel lighting by default - * Simplify shader pipeline? - * Update examples - * Simplify shape pipeline - * One rendering method for shapes, construct geom object from retain mode + +- Mobile device support / upgrades +- WebGL robustness + - Lighting + - lights() implementation + - Per pixel lighting by default + - Simplify shader pipeline? + - Update examples + - Simplify shape pipeline + - One rendering method for shapes, construct geom object from retain mode ## New Features -* Shapes - * createShape() public API for returning a p5.Geometry object?? What is the equivalent in 2D? - * Anything SVG for 2D? -* GIF support with image() - * createImg is a good work around but students love gifs :) -* i18n (localisation) for error messages + FES - * Develop infrastructure for localizing error and console messages within the codebase - * Translate various error messages -* K-12 support as discussed [in thread](https://github.com/processing/p5.js/issues/2305) - * add circle() and square() - * Get makeyourownalgorithmicart/simple.js on libraries page -* Implement Promises across the library - * Confirm/implement use of Promises across codebase - * Open use of Promises in API for loadX() functions (lower priority?) - + +- Shapes + - createShape() public API for returning a p5.Geometry object?? What is the equivalent in 2D? + - Anything SVG for 2D? +- GIF support with image() + - createImg is a good work around but students love gifs :) +- i18n (localisation) for error messages + FES + - Develop infrastructure for localizing error and console messages within the codebase + - Translate various error messages +- K-12 support as discussed [in thread](https://github.com/processing/p5.js/issues/2305) + - add circle() and square() + - Get makeyourownalgorithmicart/simple.js on libraries page +- Implement Promises across the library + - Confirm/implement use of Promises across codebase + - Open use of Promises in API for loadX() functions (lower priority?) + ## Contributor process and support -* Open tickets for incompleteness in various areas: - * Testing - * Feature implementation - * Translation -* Stricter use of [semantic versioning](https://semver.org/) -* Clarify process for soliciting roadmap input for future versions. + +- Open tickets for incompleteness in various areas: + - Testing + - Feature implementation + - Translation +- Stricter use of [semantic versioning](https://semver.org/) +- Clarify process for soliciting roadmap input for future versions. ## Etc -* For 1.0 release, drop a sweet new set of curated examples from artists and coders. + +- For 1.0 release, drop a sweet new set of curated examples from artists and coders. diff --git a/contributor_docs/archive/supported_browsers.md b/contributor_docs/archive/supported_browsers.md index 28fba0e4f1..7954caaeb1 100644 --- a/contributor_docs/archive/supported_browsers.md +++ b/contributor_docs/archive/supported_browsers.md @@ -3,6 +3,7 @@ # Supported browsers ## Our stated goal + p5.js uses [browserslist](https://browsersl.ist/) and [Babel](https://babeljs.io/) to provide support for older browsers. The browserslist configuration in use is [`last 2 versions, not dead`](https://browserslist.dev/?q=bGFzdCAyIHZlcnNpb25zLCBub3QgZGVhZA%3D%3D). `last 2 versions` means the last two releases of any browsers, `not dead` means browsers that had official support or updates in the past 24 months. Both of these conditions must be true for a browser to be supported. In practice, you can still use most of the latest features available in Javascript because Babel will likely be able to transpile or polyfill them to something matching the required compatibility list. Some features such as [Web API](https://developer.mozilla.org/en-US/docs/Web/API), [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API), or similar features not part of the core Javascript language cannot be handled by Babel and will need to be assessed on a case by case basis. @@ -10,8 +11,9 @@ In practice, you can still use most of the latest features available in Javascri Good places to check if a feature is available are [caniuse.com](https://caniuse.com/) and [MDN](https://developer.mozilla.org/en-US/). ## Where does this apply + The supported browsers requirement will apply to the p5.js source code, all examples (both website examples page and documentation), and all official tutorials. Third party add-on libraries does not have to adhere to the same requirement but are encouraged to do so. In many cases browsers not officially supported will likely still work with p5.js but we provide no guarantee for this case. -Stewards of each section will be responsible for ensuring PR involving code changes adhere to this requirement. \ No newline at end of file +Stewards of each section will be responsible for ensuring PR involving code changes adhere to this requirement. diff --git a/contributor_docs/contributing_to_the_p5js_reference.md b/contributor_docs/contributing_to_the_p5js_reference.md index 2343114611..61bfff89f5 100644 --- a/contributor_docs/contributing_to_the_p5js_reference.md +++ b/contributor_docs/contributing_to_the_p5js_reference.md @@ -7,18 +7,19 @@ # Contributing to the p5.js Reference ## Table of Contents -* [A quick introduction to reference comments](#a-quick-introduction-to-reference-comments) -* [Documenting functions](#documenting-functions) -* [Documenting p5.js variables](#documenting-p5js-variables) -* [Adding examples](#adding-examples) -* [Using assets in examples and descriptions](#using-assets) -* [Linking to other p5.js features](#linking) -* [Less common JSDoc tags](#less-common-jsdoc-tags) -* [Generating and previewing the reference](#generating-and-previewing-the-reference) -* [Linting the comments to find errors](#linting-the-docs) -* [Next steps](#next-steps) -* [Appendix: Tags quick reference](#tags-quick-reference) -* [Appendix: Summary of differences with p5.js v1.x](#doc-differences-v1-to-v2) + +- [A quick introduction to reference comments](#a-quick-introduction-to-reference-comments) +- [Documenting functions](#documenting-functions) +- [Documenting p5.js variables](#documenting-p5js-variables) +- [Adding examples](#adding-examples) +- [Using assets in examples and descriptions](#using-assets) +- [Linking to other p5.js features](#linking) +- [Less common JSDoc tags](#less-common-jsdoc-tags) +- [Generating and previewing the reference](#generating-and-previewing-the-reference) +- [Linting the comments to find errors](#linting-the-docs) +- [Next steps](#next-steps) +- [Appendix: Tags quick reference](#tags-quick-reference) +- [Appendix: Summary of differences with p5.js v1.x](#doc-differences-v1-to-v2) In p5.js, we author the code reference you see on the [reference](https://p5js.org/reference/) page on the p5.js website by including specialized comments alongside the library’s source code. For each p5 function, for example, a reference comment includes the name and description of the function, details of its parameters and return value, and examples of use. The content you see on each p5.js function/variable’s reference page is built from these reference comments in the source code. @@ -58,7 +59,7 @@ When you look at the source code of p5.js, you will see many lines in the librar */ ``` -These reference comments are usually followed by the actual JavaScript code that defines the function. Reference comments always start with `/**` and end with `*/`, with each line in between the two starting with `*`. +These reference comments are usually followed by the actual JavaScript code that defines the function. Reference comments always start with `/**` and end with `*/`, with each line in between the two starting with `*`. Anything in a block in this manner will be interpreted as reference documentation. These generally follow a format called [JSDoc](https://jsdoc.app/). @@ -66,14 +67,14 @@ In this style of reference comments, each comment block is further divided into ## Documenting functions -In abstract, a comment block for a p5 _function_ typically looks as follows. (We've included the tags that will mark each section but the correct syntax will be shown shortly). +In abstract, a comment block for a p5 _function_ typically looks as follows. (We've included the tags that will mark each section but the correct syntax will be shown shortly). ```js /** * One-line description of the function. * * More detail about the function's behavior. - * This can be many paragraphs and include + * This can be many paragraphs and include * links and images. * * The name of function - with @method @@ -88,13 +89,11 @@ In abstract, a comment block for a p5 _function_ typically looks as follows. (W */ ``` - - ### A look at a real example function: `sin()` -Let’s take a look at a real reference comment block for a function from the library - the `sin()` function. You can compare what you see in the comment block below with what you can see on the published [reference page for `sin()`](https://p5js.org/reference/p5/sin/). +Let’s take a look at a real reference comment block for a function from the library - the `sin()` function. You can compare what you see in the comment block below with what you can see on the published [reference page for `sin()`](https://p5js.org/reference/p5/sin/). -It is very long. If it is overwhelming, notice that it is 80% made up of example sketches. +It is very long. If it is overwhelming, notice that it is 80% made up of example sketches. The reference comment block for the `sin()` function: @@ -172,13 +171,13 @@ The reference comment block for the `sin()` function: */ ``` -If you like, you can find the source code for this `sin` function in the p5 repo, [here in /src/math/trigonometry.js](https://github.com/processing/p5.js/blob/main/src/math/trigonometry.js). Search for `@method sin` for the start of the comment block (and `fn.sin` for the start of the function code). +If you like, you can find the source code for this `sin` function in the p5 repo, [here in /src/math/trigonometry.js](https://github.com/processing/p5.js/blob/main/src/math/trigonometry.js). Search for `@method sin` for the start of the comment block (and `fn.sin` for the start of the function code). Now we'll look at each section in turn. ### Describing the function -Example: +Example: ```js /** @@ -199,11 +198,11 @@ At the very top of the comment is the text description of the function. The desc This description can contain both [markdown syntax](https://daringfireball.net/projects/markdown/syntax) and [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements). In the above example we see examples of both: -* Markdown: The backtick characters around the function name, `sin()`, will cause it to be presented as a snippet of code. -* Markdown: The blank line will be preserved. -* HTML: The use of the html anchor tag `` makes `angleMode()` a clickable link to navigate to another page of documentation. See [linking](#linking), later. +- Markdown: The backtick characters around the function name, `sin()`, will cause it to be presented as a snippet of code. +- Markdown: The blank line will be preserved. +- HTML: The use of the html anchor tag `` makes `angleMode()` a clickable link to navigate to another page of documentation. See [linking](#linking), later. -In some places, you may see the `@description` tag used. It explicitly marks the description section and is only actually necessary if the description text isn't placed at the very beginning of the JSDoc comment. +In some places, you may see the `@description` tag used. It explicitly marks the description section and is only actually necessary if the description text isn't placed at the very beginning of the JSDoc comment. [Here's the JSDoc documentation on this description section](https://jsdoc.app/tags-description). @@ -214,7 +213,7 @@ It may be presented alone with the function name in listings (e.g. in [the funct **Examples of first sentences of function descriptions** -The following examples are all taken directly from the p5.js codebase, from the description of functions. They demonstrate that even complex functions can have a short first sentence description. +The following examples are all taken directly from the p5.js codebase, from the description of functions. They demonstrate that even complex functions can have a short first sentence description. ``` Creates a canvas element on the web page. @@ -223,7 +222,7 @@ Sets the color used to draw points, lines, and the outlines of shapes. Stops the code in draw() from running repeatedly. -Creates a new p5.Element object. +Creates a new p5.Element object. Creates a p5.Graphics object. @@ -260,16 +259,16 @@ For an example of this see the reference for [`applyMatrix`](https://p5js.org/re _Hosting_ of these images is discussed later in [this section on assets](#using-assets). -To _link_ to it, you would use an HTML `img` element, with `src` property referring to the image in `assets/`. Be sure to also include an appropriate `alt` property. +To _link_ to it, you would use an HTML `img` element, with `src` property referring to the image in `assets/`. Be sure to also include an appropriate `alt` property. Here's a cut-down example from the source of [`applyMatrix`](https://github.com/processing/p5.js/blob/bf773b74975e58ad0978031338a339e990222202/src/core/transform.js#L31): ```js /** - * Applies a transformation matrix to the + * Applies a transformation matrix to the * coordinate system. * ... - * The transformation matrix used when applyMatrix is called in 2D mode. * ... @@ -277,32 +276,35 @@ Here's a cut-down example from the source of [`applyMatrix`](https://github.com/ ### `@method` - specifying the function name -[`@method`](https://jsdoc.app/tags-function) is used to define the name of the function, in this case `sin`. Note that the function name does not include the brackets `()`. +[`@method`](https://jsdoc.app/tags-function) is used to define the name of the function, in this case `sin`. Note that the function name does not include the brackets `()`. -You may sometimes see this missing from the reference. In that case JSDoc will try to infer it by looking at the name of the function from the following source code. It is recommended to include the function name using the @method tag where possible. +You may sometimes see this missing from the reference. In that case JSDoc will try to infer it by looking at the name of the function from the following source code. It is recommended to include the function name using the @method tag where possible. This tag is also useful when detailing multiple signatures for a function (see later). ### `@param` - specifying details of each parameter -[`@param`](https://jsdoc.app/tags-param) is used to define the parameters or arguments that the function accepts. It is used once per parameter. +[`@param`](https://jsdoc.app/tags-param) is used to define the parameters or arguments that the function accepts. It is used once per parameter. -The general format for a parameter is: +The general format for a parameter is: ``` @param {type} name Description here. ``` -For example, the `sin()` function has: +For example, the `sin()` function has: + ``` @param {Number} angle the angle. ``` 1. Following the keyword `@param`, the _type_ of the parameter is mentioned in curly brackets `{}`. (in this case, `{Number}`) - - This is strongly recommended but can be omitted in certain cases (notably in p5.strands). + +- This is strongly recommended but can be omitted in certain cases (notably in p5.strands). 2. After the type, the next _word_ (in this case, `angle`) is the name of the parameter. - - This must exactly match the name of the relevant parameter in the source code. + +- This must exactly match the name of the relevant parameter in the source code. 3. After the name, the rest of the line is the description of the parameter. @@ -315,9 +317,11 @@ If the parameter is optional, add square brackets around the name: #### Examples of parameter types An example from [rect()](https://p5js.org/reference/p5/rect/), where the topleft ("tl") parameter is optional: + ``` @param {Number} [tl] optional radius of top-left corner. ``` + An example from [createCanvas](https://p5js.org/reference/p5/createcanvas/) where a "renderer" parameter can optionally be supplied: ``` @@ -325,24 +329,27 @@ An example from [createCanvas](https://p5js.org/reference/p5/createcanvas/) wher ``` A more advanced example from [hue](https://p5js.org/reference/p5/hue/) where the color parameter can be a p5.Color object, an array of numbers, or a string: + ``` * @param {p5.Color|Number[]|String} color ``` -**"Number" not "number". "String" not "string"** - -Throughout the p5.js documentation, primitive types are all capitalized: String, Number, Boolean, etc. (This is an example where our convention differs from JSDoc.) +**"Number" not "number". "String" not "string"** +Throughout the p5.js documentation, primitive types are all capitalized: String, Number, Boolean, etc. (This is an example where our convention differs from JSDoc.) #### Parameters that expect arrays -When specifying an array type, use `type[]` rather than `Array`. +When specifying an array type, use `type[]` rather than `Array`. For example, use: + ``` @param {Number[]} colors an array of colors. ``` + instead of: + ``` @param {Array} colors an array of colors. ``` @@ -362,14 +369,15 @@ Examples: ```js /** - * @method textAlign - * @param {LEFT|CENTER|RIGHT} horizAlign - * horizontal alignment. + * @method textAlign + * @param {LEFT|CENTER|RIGHT} horizAlign + * horizontal alignment. */ - ``` +``` + #### Parameters that expect callback functions -If you need to document a parameter which is expected to be a _function_, you can use the type `Function`. For example: +If you need to document a parameter which is expected to be a _function_, you can use the type `Function`. For example: ``` @param {Function} callback @@ -377,25 +385,26 @@ If you need to document a parameter which is expected to be a _function_, you ca If you can detail the expected function further, use a type of this form: `function(typeOfArgument)` -Example: +Example: -Here's some of the documentation for `loadImage`, which has an optional parameter, `successCallback`. This received callback function will be passed the loaded `p5.Image` once ready: +Here's some of the documentation for `loadImage`, which has an optional parameter, `successCallback`. This received callback function will be passed the loaded `p5.Image` once ready: ``` * @method loadImage -* @param {function(p5.Image)} [successCallback] -* function called with +* @param {function(p5.Image)} [successCallback] +* function called with * p5.Image once loaded ``` #### Links in param descriptions -You can include links in the description of a parameter. For how to link to another reference page see [linking](#linking), later. +You can include links in the description of a parameter. For how to link to another reference page see [linking](#linking), later. Example: + ``` * @method rotate -* @param {Number} angle angle of rotation in +* @param {Number} angle angle of rotation in * the current angleMode(). ``` @@ -404,6 +413,7 @@ Example: [`@return`](https://jsdoc.app/tags-returns) is used to define the return value of the function. The general format for this line is: + ``` @return {type} Description here. ``` @@ -414,32 +424,35 @@ Example, from `sin()` function: @return {Number} sine of the angle. ``` -1. Following the keyword `@return`, the _type_ of the return value is stated in curly brackets `{}`. (e.g. `{Number}` for the `sin()` function) +1. Following the keyword `@return`, the _type_ of the return value is stated in curly brackets `{}`. (e.g. `{Number}` for the `sin()` function) 2. After the type, the rest of the line is the description of the return value. If the function does not return a value, the `@return` tag can be left out. If the function returns a value but you want it to be type `any`, use `{*}`, e.g. `@return {*} description here...` -You can include links in the description of a return. For how to link to another reference page see [linking](#linking), later. +You can include links in the description of a return. For how to link to another reference page see [linking](#linking), later. More examples of `@return`: -Linking to the documentation of the returned object. (From: [createVector()](https://p5js.org/reference/p5/createVector/)): +Linking to the documentation of the returned object. (From: [createVector()](https://p5js.org/reference/p5/createVector/)): + ``` -@return {p5.Vector} new +@return {p5.Vector} new p5.Vector object. ``` Returning an array. (From [p5.Image#get()](https://p5js.org/reference/p5.image/get/)): + ``` @return {Number[]} color of the pixel at (x, y) in array format `[R, G, B, A]`. ``` -Returning a Promise. (From [loadFilterShader()](https://p5js.org/reference/p5/loadFilterShader/)): +Returning a Promise. (From [loadFilterShader()](https://p5js.org/reference/p5/loadFilterShader/)): + ``` -@return {Promise} a promise that +@return {Promise} a promise that resolves with a shader object. ``` @@ -460,7 +473,7 @@ If a function has multiple possible parameter options, you can specify each indi ```js /** * @method background - * @param {String} colorstring color string, + * @param {String} colorstring color string, * possible formats include: integer rgb() * or rgba(), percentage rgb() or rgba(), * 3-digit hex, 6-digit hex @@ -469,33 +482,31 @@ If a function has multiple possible parameter options, you can specify each indi /** * @method background - * @param {Number} gray specifies a value + * @param {Number} gray specifies a value * between white and black * @param {Number} [a] */ ``` - #### Multiple signatures vs optional parameter It is not necessary to create a separate signature if the only difference between two signatures is the addition of an optional parameter. Limit the use of this feature if possible because it can create unnecessary noise in the reference. - #### Static and instance methods with same name When a class provides instance and static methods of the same name, only one will currently be presented in the online reference. -Document the instance method as normal. However, at the end of its description, mention that there is also a static version and describe its parameters and return in plain text. +Document the instance method as normal. However, at the end of its description, mention that there is also a static version and describe its parameters and return in plain text. This is the documentation that will be presented in the reference. -In a separate comment block for the static method, document its parameters and return value. Even though these will not currently be shown on the reference website, they're important in the generation of the library's type declarations (e.g. for intellisense and auto-completion) (and may eventually be incorporated into the reference page). +In a separate comment block for the static method, document its parameters and return value. Even though these will not currently be shown on the reference website, they're important in the generation of the library's type declarations (e.g. for intellisense and auto-completion) (and may eventually be incorporated into the reference page). -Example: +Example: The p5.Vector class has an instance method [add()](https://p5js.org/reference/p5.vector/add/), and a static `add()` method, too. -Here's (a simplification of) the documentation of the _instance_ method. Note that the description also mentions the static version. +Here's (a simplification of) the documentation of the _instance_ method. Note that the description also mentions the static version. ```js /** @@ -511,12 +522,12 @@ Here's (a simplification of) the documentation of the _instance_ method. Note t * p5.Vector object and doesn't change the * originals. * - * @param {Number|Array} x x component of - * the vector to be added or an array of + * @param {Number|Array} x x component of + * the vector to be added or an array of * components. - * @param {Number} [y] y component of the + * @param {Number} [y] y component of the * vector to be added. - * @param {Number} [z] z component of the + * @param {Number} [z] z component of the * vector to be added. * * @example @@ -526,19 +537,20 @@ add(...args) { ... } ``` + Here is the documentation of the static method: ```js // Adds two vectors together and returns a new one. /** * @static - * @param {p5.Vector} v1 A + * @param {p5.Vector} v1 A * p5.Vector to add - * @param {p5.Vector} v2 A + * @param {p5.Vector} v2 A * p5.Vector to add - * @param {p5.Vector} [target] vector to + * @param {p5.Vector} [target] vector to * receive the result. - * @return {p5.Vector} resulting + * @return {p5.Vector} resulting * p5.Vector. */ static add(v1, v2, target) { @@ -553,7 +565,7 @@ For dynamically generated methods, do the same as usual, but add `@for p5`. ```js function myAddon(p5, fn) { for (const key of ['nameA', 'nameB']) { - fn[key] = function() { + fn[key] = function () { return `Hello from ${key}!`; }; } @@ -574,20 +586,21 @@ function myAddon(p5, fn) { The types specified for function parameters, return values, variables, and object properties are used not only to build the online reference but also to build type-declaration files. -These type-declaration files have multiple uses. For example: -* providing autocompletion and intellisense in IDEs, including web editors -* type-checking JavaScript or TypeScript sketches. +These type-declaration files have multiple uses. For example: + +- providing autocompletion and intellisense in IDEs, including web editors +- type-checking JavaScript or TypeScript sketches. This makes it even more desirable that we correctly specify the exact type of each element of the library. However, in a very few specialized cases, the normal way of documenting these types cannot support exactly what you need to say. -In this rare situation, it is possible to use a last-resort mechanism to inject the correct types directly into the type-declaration files. For details on this see [Patching types](./reference_generation_process#patching-types) in the reference generation process documentation. +In this rare situation, it is possible to use a last-resort mechanism to inject the correct types directly into the type-declaration files. For details on this see [Patching types](./reference_generation_process#patching-types) in the reference generation process documentation. -Bear in mind this is a last resort. Try to correctly specify the types in the documentation comments wherever possible. +Bear in mind this is a last resort. Try to correctly specify the types in the documentation comments wherever possible. ## Documenting p5.js variables -So far, we have looked at how to write references for functions. Variables follow the same structure but use different tags. +So far, we have looked at how to write references for functions. Variables follow the same structure but use different tags. Example: the reference comment block for the built-in variable, `mouseX`: @@ -610,19 +623,20 @@ Example: the reference comment block for the built-in variable, `mouseX`: * function draw() { * background(244, 248, 252); * line(mouseX, 0, mouseX, 100); - * describe('horizontal black line moves + * describe('horizontal black line moves * left and right with mouse x-position'); * } */ ``` -The start of the block contains the description of the variable (`mouseX` in this case). The same rules apply from function descriptions: Use a clear short first line, you can use markdown and HTML and assets in the description. +The start of the block contains the description of the variable (`mouseX` in this case). The same rules apply from function descriptions: Use a clear short first line, you can use markdown and HTML and assets in the description. ### The `@property` tag To define the name of the variable, we use [`@property`](https://jsdoc.app/tags-property) instead of `@method`. `@property` follows the same syntax as `@param` for defining the type and its name. -Syntax: +Syntax: + ``` @property {type} name ``` @@ -630,6 +644,7 @@ Syntax: Examples: From [`src/events/pointer.js`](https://github.com/processing/p5.js/blob/main/src/events/pointer.js): + ``` * An `Array` of all the current touch points on a touchscreen device. * ... @@ -637,6 +652,7 @@ From [`src/events/pointer.js`](https://github.com/processing/p5.js/blob/main/src ``` From [`src/image/p5.Image.js`](https://github.com/processing/p5.js/blob/main/src/image/p5.Image.js): + ``` * An array containing the color of each pixel on the canvas. * ... @@ -678,7 +694,7 @@ It should declare a `setup()` function, and `draw()` if required. The example’s canvas should be 100x100 pixels. -If there's no sensible way to present the code usage in a small sketch, you can create an example which doesn't run. See [norender](#norender). +If there's no sensible way to present the code usage in a small sketch, you can create an example which doesn't run. See [norender](#norender). ### Providing multiple examples @@ -715,11 +731,11 @@ Example (from `createCanvas()`): ### Inserting examples _within_ the description -It is possible, and often desirable, to include early runnable examples _within_ the description section, before the main "Examples" section of the page. This is particularly useful when a function or class has a lengthy and detailed description section. +It is possible, and often desirable, to include early runnable examples _within_ the description section, before the main "Examples" section of the page. This is particularly useful when a function or class has a lengthy and detailed description section. The p5.Image reference has a good example of this. ([Source here](https://github.com/processing/p5.js/blob/6a61f7fb3055969fe53d9f82027f891d245b3e9f/src/webgl/material.js#L597)). -To add such an example, instead of using an `@example` tag, surround the example code in a "fenced" markdown code-block with the annotation `js example`. Here's an example of how it should look: +To add such an example, instead of using an `@example` tag, surround the example code in a "fenced" markdown code-block with the annotation `js example`. Here's an example of how it should look: ```` * ```js example @@ -731,7 +747,7 @@ To add such an example, instead of using an `@example` tag, surround the example ```` To make the code-block, surround your example code before and after by three backticks on a newline. -The opening backticks should be immediately followed by the annotation `js example`. Remember, you should not use an `@example` tag in this case. +The opening backticks should be immediately followed by the annotation `js example`. Remember, you should not use an `@example` tag in this case. Why is this important? @@ -739,15 +755,15 @@ Early examples like this allow the reader to quickly play with, and get an early ### About the complexity of code examples -The basic principle of writing good example code for the reference is to keep things simple and minimal. The example should be meaningful and explain how the feature works without being too complicated. +The basic principle of writing good example code for the reference is to keep things simple and minimal. The example should be meaningful and explain how the feature works without being too complicated. -While it may be tempting to make a more interesting, engaging, or "cool" example using other functions (e.g. noise()), or using clever math, that makes it harder for readers to understand. Try to minimize the number of concepts used in your example. +While it may be tempting to make a more interesting, engaging, or "cool" example using other functions (e.g. noise()), or using clever math, that makes it harder for readers to understand. Try to minimize the number of concepts used in your example. We won’t go through the details about best practices and code style for the example code here; please see the [reference style guide](https://p5js.org/contribute/documentation_style_guide/), instead. ### When examples omit setup() -If the `setup()` function is not included, such as in the example above, the code will be automatically wrapped in a `setup()` function with a default 100x100 pixels gray background canvas created. While you may see such examples included in this guide for brevity, it is generally preferred that your example be a complete sketch. This makes it easier for learners to see the intended usage in full context and copy-paste to get a complete runnable example in their own editor. +If the `setup()` function is not included, such as in the example above, the code will be automatically wrapped in a `setup()` function with a default 100x100 pixels gray background canvas created. While you may see such examples included in this guide for brevity, it is generally preferred that your example be a complete sketch. This makes it easier for learners to see the intended usage in full context and copy-paste to get a complete runnable example in their own editor. ### Preventing execution of example code with `norender` @@ -763,21 +779,24 @@ If you do not want the reference page to run an example's code (i.e., you only w ## Using assets in examples and descriptions If your example code (or the description section) uses asset files (e.g. images, fonts, sounds), here's how to work with them. + 1. Add the asset file(s) in the _p5.js-website_ repo's [/public/assets](https://github.com/processing/p5.js-website/tree/2.0/public/assets) folder. 2. Refer to the asset with the path `assets/filename.ext` in the code example or description. -Examples: +Examples: For a full working example, see the [tint()](https://p5js.org/reference/p5/tint/) reference. ```js img = await loadImage('assets/rockies.jpg'); ``` + The above code will load the image file, [/public/assets/rockies.jpg](https://github.com/processing/p5.js-website/blob/2.0/public/assets/rockies.jpg), stored in the p5.js-website repo. ```js font = await loadFont('assets/inconsolata.otf'); ``` + The above code will load the font file, [/public/assets/inconsolata.otf](https://github.com/processing/p5.js-website/blob/2.0/public/assets/inconsolata.otf), stored in the p5.js-website repo. #### Asset hosting differs from p5 v1 @@ -786,7 +805,7 @@ Note that hosting in the _p5.js-website repo_ is a significant change from p5 v1 ### Re-using existing assets -You may wish to make use of an asset that's already in that directory, linking to it in the same way. (Reusing assets also provides familiarity to the reader across the documentation). You can see all such assets [here](https://github.com/processing/p5.js-website/tree/2.0/public/assets). (Cloning that repo and using a file explorer to open `public/assets` will make it easier to browse the assets.) +You may wish to make use of an asset that's already in that directory, linking to it in the same way. (Reusing assets also provides familiarity to the reader across the documentation). You can see all such assets [here](https://github.com/processing/p5.js-website/tree/2.0/public/assets). (Cloning that repo and using a file explorer to open `public/assets` will make it easier to browse the assets.) ### Hotlinking asset files in examples @@ -826,12 +845,11 @@ Finally, for every example you add, you are required to use the p5.js function ` For more on `describe()` visit the [web accessibility contributor documentation](./web_accessibility/#describe), and the [Writing Accessible Canvas Descriptions](https://p5js.org/tutorials/writing-accessible-canvas-descriptions/) tutorial. - ## Linking to other p5.js features A hyperlink can be used within a description or `@param` or `@return` to link to the reference documentation of another relevant p5.js feature. -Example: +Example: ``` circle() @@ -839,50 +857,52 @@ Example: Note that the URL should _not_ include a direct link to the website. -With the [exception of constants](#linking-to-constants), the URL should be of the following "fragment" or "hash" form: +With the [exception of constants](#linking-to-constants), the URL should be of the following "fragment" or "hash" form: `#/class/property` Example URLs linking to functions and variables: -* `#/p5/circle` -* `#/p5/createCanvas` -* `#/p5/fill` -* `#/p5/mouseX` -* `#/p5.Vector/lerp` -* `#/p5.Vector/dist` -* `#/p5.Image/width` +- `#/p5/circle` +- `#/p5/createCanvas` +- `#/p5/fill` +- `#/p5/mouseX` +- `#/p5.Vector/lerp` +- `#/p5.Vector/dist` +- `#/p5.Image/width` Example URLs linking to classes: -* `#/p5.Color` -* `#/p5.Image` -* `#/p5.Vector` +- `#/p5.Color` +- `#/p5.Image` +- `#/p5.Vector` As seen above, when there's no property involved, just a class itself, the second slash is generally omitted. Full examples: -From the description of [dist](https://p5js.org/reference/p5/dist/) in [calculation.js](https://github.com/processing/p5.js/blob/main/src/math/calculation.js ) +From the description of [dist](https://p5js.org/reference/p5/dist/) in [calculation.js](https://github.com/processing/p5.js/blob/main/src/math/calculation.js) + ``` -* Use p5.Vector.dist() -* to calculate the distance between two +* Use p5.Vector.dist() +* to calculate the distance between two * p5.Vector objects. ``` From the description of [pmouseX](https://p5js.org/reference/p5/pmousex/): + ``` * Its value is mouseX * from the previous frame. ``` From the `@return` statement of [createVector()](https://p5js.org/reference/p5/createVector/): + ``` -* @return {p5.Vector} new +* @return {p5.Vector} new * p5.Vector object. ``` - **Exception: Linking to constants** Links to constants should instead follow this form: @@ -891,9 +911,9 @@ Links to constants should instead follow this form: Example URLS: -* `#/p5/constants/DEGREES` -* `#/p5/constants/CENTER` -* `#/p5/constants/LEFT` +- `#/p5/constants/DEGREES` +- `#/p5/constants/CENTER` +- `#/p5/constants/LEFT` ## Less common JSDoc tags @@ -901,17 +921,17 @@ With all the above you should have most of the tools needed to write and edit p5 ### `@private` tag -You can use the `@private` if a property or variable or class is private. If a feature is marked as `@private` it will not be included as part of the rendered reference on the website. This is done automatically for methods whose names start with `_`. +You can use the `@private` if a property or variable or class is private. If a feature is marked as `@private` it will not be included as part of the rendered reference on the website. This is done automatically for methods whose names start with `_`. The tag is used when documenting internal features for the library itself. -Example: +Example: Here's `invert` from `src/image/filters.js`: ```js /** - * Sets each pixel to its inverse value. + * Sets each pixel to its inverse value. * No parameter is used. * @private * @param {Canvas} canvas @@ -925,9 +945,9 @@ invert(canvas) { #### `@module` and `@submodule` -At the top of each source code file will be a comment block with a `@module` tag. A module is a top-level grouping of features in the reference pages on the website. This does _not_ necessarily correspond to any specific software `module` concept in the code itself. +At the top of each source code file will be a comment block with a `@module` tag. A module is a top-level grouping of features in the reference pages on the website. This does _not_ necessarily correspond to any specific software `module` concept in the code itself. -After the `@module` tag, an optional `@submodule` tag can be used to further group features for the website. This `@submodule` tag can be set differently on any comment block in the file to change the submodule for a specific feature. +After the `@module` tag, an optional `@submodule` tag can be used to further group features for the website. This `@submodule` tag can be set differently on any comment block in the file to change the submodule for a specific feature. The convention p5.js follows is that each subfolder in the `src/` folder will be one `@module` while each file inside the subfolder will be its own `@submodule` under the overall subfolder’s `@module`. Unless you are adding new subfolders/files to the p5.js source code, you shouldn’t need to edit a file's top-level reference comment block. @@ -937,14 +957,15 @@ Examples of module + submodule groupings The ["Image" module](https://p5js.org/reference/#Image) in `src/image/` has the following submodules: "Loading & Displaying", "Pixels", and "p5.Image". -The ["Color" module](https://p5js.org/reference/#Color) in `src/color/` has the following submodules: - "Creating & Reading", "Setting", and "Color Conversion". +The ["Color" module](https://p5js.org/reference/#Color) in `src/color/` has the following submodules: +"Creating & Reading", "Setting", and "Color Conversion". -The _conceptual_ ["3D" module](https://p5js.org/reference/#3D) documents code mostly from `src/webgl/` (there is no `src/3D/`), and has the following submodules: "Camera", "Interaction", "Lights", "Material", "strands", "p5.Camera", "p5.Shader". +The _conceptual_ ["3D" module](https://p5js.org/reference/#3D) documents code mostly from `src/webgl/` (there is no `src/3D/`), and has the following submodules: "Camera", "Interaction", "Lights", "Material", "strands", "p5.Camera", "p5.Shader". Examples: -For just a category: +For just a category: + ``` /** * @module Rendering @@ -967,6 +988,7 @@ The `@for` tag defines the relationship between this module and the overall `p5` It is also used when documenting class properties, to ensure the documentation attaches to the correct class. (Currently class properties are documented _outside_ their class.) Example: + ``` /** * The x component of the vector @@ -979,47 +1001,48 @@ Example: #### The @beta tag - marking experimental API features -This tag is used to mark that a feature is experimental and that its details may change or it may be removed. A warning will be presented explaining this on the reference page. +This tag is used to mark that a feature is experimental and that its details may change or it may be removed. A warning will be presented explaining this on the reference page. It should be placed on a separate line in the comment block and does not need any argument. -```js - /** - * Create a new shader that can change how - * fills are drawn. - * - * ...omitted... - * - * @method buildMaterialShader - * @submodule p5.strands - * @beta - * @param {Function} callback A function - * building a p5.strands shader. - * @returns {p5.Shader} The material shader. - */ +```js +/** + * Create a new shader that can change how + * fills are drawn. + * + * ...omitted... + * + * @method buildMaterialShader + * @submodule p5.strands + * @beta + * @param {Function} callback A function + * building a p5.strands shader. + * @returns {p5.Shader} The material shader. + */ ``` #### The @deprecated tag Marks that a feature will be removed from a future version of p5.js, possibly also indicating a better option. -Follow the tag with: -* Text explaining that the feature will be removed from a future version. -* (If possible) A suggested replacement to be used (with link). +Follow the tag with: + +- Text explaining that the feature will be removed from a future version. +- (If possible) A suggested replacement to be used (with link). Example (imagined): ```js /** -* Create a p5.Vector from polar coordinates. -* -* @method createVectorFromPolarCoordinates -* @deprecated This will be removed from a -* future version of p5.js. -* -* p5.Vector.fromAngle() is recommended, -* instead. -*/ + * Create a p5.Vector from polar coordinates. + * + * @method createVectorFromPolarCoordinates + * @deprecated This will be removed from a + * future version of p5.js. + * + * p5.Vector.fromAngle() is recommended, + * instead. + */ ``` Example (real): @@ -1040,11 +1063,12 @@ Example (real): * */ ``` + ### Creating and documenting classes It's unlikely you'll need the following more advanced material unless you're creating new classes. -Classes are to be created *outside* of the addon function, and assigned to `p5` *inside*. The class name should be the same always: +Classes are to be created _outside_ of the addon function, and assigned to `p5` _inside_. The class name should be the same always: ```js class MyClass { @@ -1052,17 +1076,17 @@ class MyClass { } export default function myAddon(p5, fn) { - p5.MyClass = MyClass; + p5.MyClass = MyClass; } ``` -Document class methods directly above the members in classes, *without* a `@method` tag: +Document class methods directly above the members in classes, _without_ a `@method` tag: ```js class MyClass { /** * Description goes here - */ + */ myMethod() { return 4; } @@ -1081,11 +1105,11 @@ class MyClass { export default function myAddon(p5, fn) { /** * Description of the class goes here! - * - * @class p5.MyClass - * @param {Number} n A number to pass in - */ - p5.MyClass = MyClass; + * + * @class p5.MyClass + * @param {Number} n A number to pass in + */ + p5.MyClass = MyClass; } ``` @@ -1101,27 +1125,27 @@ function color(p5, fn, lifecycles){ * A class to describe a color. * * Each `p5.Color` object stores the color mode - * and level maxes that were active during its - * construction. These values are used to - * interpret the arguments passed to the - * object's constructor. They also determine + * and level maxes that were active during its + * construction. These values are used to + * interpret the arguments passed to the + * object's constructor. They also determine * output formatting such as when - * saturation() + * saturation() * is called. * - * Color is stored internally as an array of - * ideal RGBA values in floating point form, - * normalized from 0 to 1. These values are + * Color is stored internally as an array of + * ideal RGBA values in floating point form, + * normalized from 0 to 1. These values are * used to calculate the closest screen colors, * which are RGBA levels from 0 to 255. Screen * colors are sent to the renderer. * - * When different color representations are - * calculated, the results are cached for - * performance. These values are normalized, + * When different color representations are + * calculated, the results are cached for + * performance. These values are normalized, * floating-point numbers. * - * Note: color() is + * Note: color() is * the recommended way to create an instance of * this class. * @@ -1139,7 +1163,6 @@ function color(p5, fn, lifecycles){ p5.Color = Color; ``` - Documentation for class properties should appear after the class is added to `p5`, not within the class itself. It needs to have the `@for` tag referencing its class, and the `@property` tag naming the property itself: ```js @@ -1153,17 +1176,17 @@ class MyClass { export default function myAddon(p5, fn) { /** * Description of the class goes here! - * - * @class p5.MyClass - */ - p5.MyClass = MyClass; - - /** - * Description of the property goes here! - * - * @property {Number} myProperty - * @for p5.MyClass - */ + * + * @class p5.MyClass + */ + p5.MyClass = MyClass; + + /** + * Description of the property goes here! + * + * @property {Number} myProperty + * @for p5.MyClass + */ } ``` @@ -1171,15 +1194,15 @@ export default function myAddon(p5, fn) { ### A quick first in-editor preview for vs code users -In some editors, such as vs code, you can hover over a function or variable to see immediately - in a pop-up - a rendering of the information from the documentation above it. This can be a useful first quick preview, though it won't show everything - it won't run your code examples, or show linked images, for example. +In some editors, such as vs code, you can hover over a function or variable to see immediately - in a pop-up - a rendering of the information from the documentation above it. This can be a useful first quick preview, though it won't show everything - it won't run your code examples, or show linked images, for example. ### Previewing your work on the website, locally -At some point you will want to preview how your changes will look on the website. This involves running the website locally and having it import your p5.js code from a branch of your repo. +At some point you will want to preview how your changes will look on the website. This involves running the website locally and having it import your p5.js code from a branch of your repo. -Steps: +Steps: -1. Commit your changes to a local branch of your fork of the p5.js repo. The changes don't need to be pushed to github for this purpose, but they do need to be committed on a branch. +1. Commit your changes to a local branch of your fork of the p5.js repo. The changes don't need to be pushed to github for this purpose, but they do need to be committed on a branch. 1. Clone [the p5.js-website repo](https://github.com/processing/p5.js-website/tree/2.0) locally. 1. Open a terminal in your new p5.js-website repo 1. Check out the branch "2.0" @@ -1198,11 +1221,11 @@ For example, if your work is in a branch called `my-amazing-branch` on a local p npm run custom:dev ../p5.js#my-amazing-branch ``` -This will build the local website reference pages from the data in your branch and then start a development preview of the website. +This will build the local website reference pages from the data in your branch and then start a development preview of the website. 6. Find the URL that is logged in the console and visit it in your browser in order to test out your changes. -If you prefer to preview work that's already on github, you can do so. Use the repo URL instead of its local path, as follows: +If you prefer to preview work that's already on github, you can do so. Use the repo URL instead of its local path, as follows: (Again, note the following is a single line, not two lines!) @@ -1213,34 +1236,33 @@ npm run custom:dev https://github.com/yourUsername/p5.js.git#yourBranch #### Resetting your changes When you're done with this preview, you can run this command to reset your changes: + ```sh npm run custom:cleanup ``` #### Troubleshooting -* Review the log from the above `custom:dev` process, for mentions of your code. -* Don't forget that if you're [using local asset files](#using-assets), they'll need to be in the *website* repo, _not_ the main p5.js repo! -* Note that block comments starting with `/*` rather than `/**` will be *ignored* and won't be used for the reference (even though they may look otherwise identical in your editor). -* Make sure you've specified the correct branch where your changes are. -* Ensure you see in the log that your repo _has_ actually been cloned. There is a caching mechanism in the website build process which prevents a recently cloned repo from being cloned again. Removing the website folder `in/p5.js/` will force the build process to make a new clone. -* You can also [Run the documentation linter](#linting-the-docs) on your source files to look for warnings. - +- Review the log from the above `custom:dev` process, for mentions of your code. +- Don't forget that if you're [using local asset files](#using-assets), they'll need to be in the _website_ repo, _not_ the main p5.js repo! +- Note that block comments starting with `/*` rather than `/**` will be _ignored_ and won't be used for the reference (even though they may look otherwise identical in your editor). +- Make sure you've specified the correct branch where your changes are. +- Ensure you see in the log that your repo _has_ actually been cloned. There is a caching mechanism in the website build process which prevents a recently cloned repo from being cloned again. Removing the website folder `in/p5.js/` will force the build process to make a new clone. +- You can also [Run the documentation linter](#linting-the-docs) on your source files to look for warnings. #### Limitations -The website won't be _fully_ functional when partially prepared in this way. Notably: - -* Links between pages may be broken: - * You'll need to ensure local links end with a trailing slash '/', to be matched by Astro. -* The search facility will not work by default - * Look into `npm run build:search` to build the necessary index files. - * ...or just find your function/variable in the [top-level listing](https://p5js.org/reference/). +The website won't be _fully_ functional when partially prepared in this way. Notably: +- Links between pages may be broken: + - You'll need to ensure local links end with a trailing slash '/', to be matched by Astro. +- The search facility will not work by default + - Look into `npm run build:search` to build the necessary index files. + - ...or just find your function/variable in the [top-level listing](https://p5js.org/reference/). ## Linting the comments to find errors -The documentation.js tool can also be used for "linting". This is a process which will look for content in the comment blocks that the tool doesn't understand, or thinks might be incorrect. +The documentation.js tool can also be used for "linting". This is a process which will look for content in the comment blocks that the tool doesn't understand, or thinks might be incorrect. If something from your documentation comments is not showing up correctly on the reference site, running the linting tool may yield some clues. Alternatively, you might just want to run it to try to find any potential problems _before_ you commit. @@ -1251,9 +1273,9 @@ We can run it from the command line, in the p5.js repo: npx documentation lint "src/**/*.js" ``` -This will produce _lots_ of warnings, across many files! These do not necessarily mean there is a problem: for various reasons, the codebase uses some documentation tags that documentation.js doesn't understand (but tolerates) in order to organize the reference. +This will produce _lots_ of warnings, across many files! These do not necessarily mean there is a problem: for various reasons, the codebase uses some documentation tags that documentation.js doesn't understand (but tolerates) in order to organize the reference. -However, you can look through for any warnings relating to the file _you_ are working on, to see if anything stands out. (e.g. a misspelled tag, mismatching parameter name, or "Unknown content") +However, you can look through for any warnings relating to the file _you_ are working on, to see if anything stands out. (e.g. a misspelled tag, mismatching parameter name, or "Unknown content") ### Example of a minor issue found by the linter @@ -1264,13 +1286,13 @@ However, you can look through for any warnings relating to the file _you_ are wo The above warning says that there's a problem: -* in the file `p5.TableRow.js` -* on line 153, anywhere from character 1 -* where @param "rowID" was documented... -* ...but there's no parameter of that name in the function that follows. - * (It has inferred we must be talking about "r" - the only parameter the function has.) +- in the file `p5.TableRow.js` +- on line 153, anywhere from character 1 +- where @param "rowID" was documented... +- ...but there's no parameter of that name in the function that follows. + - (It has inferred we must be talking about "r" - the only parameter the function has.) -Here's a minimal example of hypothetical code in `p5.TableRow.js` that would have caused this warning. (Content unrelated to the warning is not shown here). +Here's a minimal example of hypothetical code in `p5.TableRow.js` that would have caused this warning. (Content unrelated to the warning is not shown here). ```js /** @@ -1285,68 +1307,68 @@ getRow (r) { ## Next steps -* If you haven't already, be sure to read the [contributor guidelines](./contributor_guidelines/). -* If you want more detail on JSDoc tags, check out the official documentation [JSDoc](https://jsdoc.app/). -* If you're curious, you can read about p5.js's [reference generation process](./reference_generation_process/) (but it's not required!) -* For examples of issues related to the reference, have a look at [#6519](https://github.com/processing/p5.js/issues/6519) and [#6045](https://github.com/processing/p5.js/issues/6045). +- If you haven't already, be sure to read the [contributor guidelines](./contributor_guidelines/). +- If you want more detail on JSDoc tags, check out the official documentation [JSDoc](https://jsdoc.app/). +- If you're curious, you can read about p5.js's [reference generation process](./reference_generation_process/) (but it's not required!) +- For examples of issues related to the reference, have a look at [#6519](https://github.com/processing/p5.js/issues/6519) and [#6045](https://github.com/processing/p5.js/issues/6045). ## Tags quick reference Click on any tag to go to its section in the text. -| Tag | Usage | Notes | -|------------------------------|----------------------------------|----------------------------------------------------| -| [@method](#method) | @method name | Omitted in certain cases. | -| [@description](#description) | @description text here | If omitted, uses first text before _any_ tag. | -| [@param](#param) | @param \{type\} name Description | Name must match the function parameter. | -| [@return](#return) | @return \{type\} Description | Omit type if it returns undefined. | -| [@example](#adding-examples) | @example
    //.. example code | Each gets `@example` before and a newline after. | -| [\`\`\`js example](#inserted-examples) | \`\`\`js example
    //...
    \`\`\` | Insert early examples _within_ the description. | -| [@property](#property) | @property \{type\} name | Most p5 variables are properties of p5 class. | -| [@readonly](#readonly) | @readonly | Mark that a property is not writeable by the user. | -| [@static](#static) | @static | Static method or property of a class. | -| [@module](#module) | @module | Groups features for display. | -| [@submodule](#module) | @submodule | Groups features for display. | -| [@class](#class) | @class | Marks a class. | -| [@for](#for) | @for className | Associate a feature with a class. | -| [@chainable](#chaining) | @chainable | Instead of `@return`, if it returns same object. | -| [@beta](#beta) | @beta | Mark an experimental feature. | -| [@deprecated](#deprecated) | @deprecated | A deprecated feature is intended to be removed. | -| [@private](#private) | @private | Internal details not for publishing. | - +| Tag | Usage | Notes | +| -------------------------------------- | ------------------------------------- | -------------------------------------------------- | +| [@method](#method) | @method name | Omitted in certain cases. | +| [@description](#description) | @description text here | If omitted, uses first text before _any_ tag. | +| [@param](#param) | @param \{type\} name Description | Name must match the function parameter. | +| [@return](#return) | @return \{type\} Description | Omit type if it returns undefined. | +| [@example](#adding-examples) | @example
    //.. example code | Each gets `@example` before and a newline after. | +| [\`\`\`js example](#inserted-examples) | \`\`\`js example
    //...
    \`\`\` | Insert early examples _within_ the description. | +| [@property](#property) | @property \{type\} name | Most p5 variables are properties of p5 class. | +| [@readonly](#readonly) | @readonly | Mark that a property is not writeable by the user. | +| [@static](#static) | @static | Static method or property of a class. | +| [@module](#module) | @module | Groups features for display. | +| [@submodule](#module) | @submodule | Groups features for display. | +| [@class](#class) | @class | Marks a class. | +| [@for](#for) | @for className | Associate a feature with a class. | +| [@chainable](#chaining) | @chainable | Instead of `@return`, if it returns same object. | +| [@beta](#beta) | @beta | Mark an experimental feature. | +| [@deprecated](#deprecated) | @deprecated | A deprecated feature is intended to be removed. | +| [@private](#private) | @private | Internal details not for publishing. | ## Appendix: Summary of documentation differences between p5.js v1 and v2 ### Overview: Syntax and tooling per p5.js version -* in p5 v2, documentation comments are: - * written in [JSDoc syntax](https://jsdoc.app/). - * processed with a [tool called documentation.js](https://documentation.js.org/). - * (Some non-JSDoc tags remain in use as the p5 documentation build system is quite customized.) +- in p5 v2, documentation comments are: + - written in [JSDoc syntax](https://jsdoc.app/). + - processed with a [tool called documentation.js](https://documentation.js.org/). + - (Some non-JSDoc tags remain in use as the p5 documentation build system is quite customized.) -* in p5 v1, documentation comments are: - * written in [YUIDoc syntax](https://yui.github.io/yuidoc/syntax/index.html) (similar) - * processed with a [tool called YUIDoc](https://yui.github.io/yuidoc/). +- in p5 v1, documentation comments are: + - written in [YUIDoc syntax](https://yui.github.io/yuidoc/syntax/index.html) (similar) + - processed with a [tool called YUIDoc](https://yui.github.io/yuidoc/). ### Syntax differences between YUIDoc and JSDoc -YUIDoc and JSDoc are largely compatible. Here are some differences. +YUIDoc and JSDoc are largely compatible. Here are some differences. -* YUIDoc requires function names be provided with the `@method` tag, whereas JSDoc can generally extract that information from the implementation source code. +- YUIDoc requires function names be provided with the `@method` tag, whereas JSDoc can generally extract that information from the implementation source code. -* JSDoc does not have either `@submodule` or `@for` tags (YUIDoc does) but we continue to use them throughout the v2.x codebase as a mechanism by which we group functions, variables, and classes on the website. If you run `documentation lint` you'll see complaints about these tags but Documentation.js collects the tagged information into its output without problem for the subsequent build-processes to use. +- JSDoc does not have either `@submodule` or `@for` tags (YUIDoc does) but we continue to use them throughout the v2.x codebase as a mechanism by which we group functions, variables, and classes on the website. If you run `documentation lint` you'll see complaints about these tags but Documentation.js collects the tagged information into its output without problem for the subsequent build-processes to use. ### Other documentation differences between versions -* Hosting assets: For v2, code example assets (images, fonts, etc) go in the website repo's `public/assets` folder. In v1 these went in the `docs/yuidoc-p5-theme/assets` folder of the p5.js repo. +- Hosting assets: For v2, code example assets (images, fonts, etc) go in the website repo's `public/assets` folder. In v1 these went in the `docs/yuidoc-p5-theme/assets` folder of the p5.js repo. -* Previewing the reference: In v2, previewing the reference site locally can no longer be done with the p5.js repo alone, but requires running build:reference on the website repo. In v1 this was possible without requiring the website repo. +- Previewing the reference: In v2, previewing the reference site locally can no longer be done with the p5.js repo alone, but requires running build:reference on the website repo. In v1 this was possible without requiring the website repo. -* Constants: In v2, constants accepted in a function parameter are shown as options in that parameter's type, whereas in v1 they are listed only in the parameter's free-text description and the type is set as (literally) `{Constant}`. +- Constants: In v2, constants accepted in a function parameter are shown as options in that parameter's type, whereas in v1 they are listed only in the parameter's free-text description and the type is set as (literally) `{Constant}`. Example: -For p5 v1.x: +For p5 v1.x: + ```js /** * @method textAlign @@ -1354,15 +1376,17 @@ For p5 v1.x: * alignment, either LEFT, CENTER, or RIGHT. * ... */ - ``` - +``` + For p5 v2.x: + ```js /** - * @method textAlign - * @param {LEFT|CENTER|RIGHT} horizAlign - * horizontal alignment. - * ... + * @method textAlign + * @param {LEFT|CENTER|RIGHT} horizAlign + * horizontal alignment. + * ... */ - ``` -* p5 v2.x no longer uses `
    ` wrappers around code examples. \ No newline at end of file +``` + +- p5 v2.x no longer uses `
    ` wrappers around code examples. diff --git a/contributor_docs/contributor_guidelines.md b/contributor_docs/contributor_guidelines.md index bb25cdff79..db4f149071 100644 --- a/contributor_docs/contributor_guidelines.md +++ b/contributor_docs/contributor_guidelines.md @@ -10,7 +10,6 @@ This is a relatively long and comprehensive document but we will try to signpost **If you are a new contributor, you may want to start with the first section, “All about issues.” If you just want a step-by-step setup of the development process, you can look at the “Quick Get Started For Developers” section.** - # Table of Contents - [All about issues](#all-about-issues) @@ -48,12 +47,10 @@ This is a relatively long and comprehensive document but we will try to signpost --- - # All about issues The majority of the activity on p5.js' GitHub repositories (repo for short) happens in issues, which is a great place to start your contribution journey. - ## What are issues? ![A cropped screenshot of the p5.js library GitHub repository, only showing contents of the top right corner. A red box is drawn on top of the screenshot surrounding the Issues tab.](images/issues-tab.png) @@ -66,7 +63,6 @@ either on the [forum](https://discourse.processing.com) or on other platforms su We have created easy-to-use issue templates to aid you in deciding whether a topic should be a GitHub issue or posted elsewhere! - ## Issue templates p5.js's issue templates make it easier for stewards and maintainers to understand and review issues. They also make it easier for you to file the relevant issue and receive a reply faster.  @@ -77,16 +73,15 @@ To file a new issue, simply go to the "Issues" tab on the p5.js repo and click o ![Cropped screenshot of the GitHub repository's issue page with the green "New issue" button highlighted with a red box surrounding it.](images/new-issue.png) +### ["Found a bug"](https://github.com/processing/p5.js/issues/new?assignees=&labels=Bug&projects=&template=found-a-bug.yml) -### ["Found a bug"](https://github.com/processing/p5.js/issues/new?assignees=\&labels=Bug\&projects=\&template=found-a-bug.yml) - -When you encounter possible incorrect behavior in p5.js or something not behaving as described in the documentation, use [this template](https://github.com/processing/p5.js/issues/new?assignees=\&labels=Bug\&projects=\&template=found-a-bug.yml). Please note that if you are trying to debug your sketch and you think it may be a problem with your code, you should first ask on the [Discourse forum](https://discourse.processing.org) instead. +When you encounter possible incorrect behavior in p5.js or something not behaving as described in the documentation, use [this template](https://github.com/processing/p5.js/issues/new?assignees=&labels=Bug&projects=&template=found-a-bug.yml). Please note that if you are trying to debug your sketch and you think it may be a problem with your code, you should first ask on the [Discourse forum](https://discourse.processing.org) instead. There are a few fields for you to fill in for this template: -1. *Most appropriate sub-area of p5.js?* - This helps us identify and respond to your issue by automatically tagging the issue with the relevant [labels](https://github.com/processing/p5.js/labels). -2. *p5.js version* - You can find the p5.js version number in either the ` ``` @@ -119,7 +133,7 @@ import trigonometry from './trigonometry.js'; import math from './math.js'; import vector from './p5.Vector.js'; -export default function(p5, fn){ +export default function (p5, fn) { p5.registerAddon(calculation); p5.registerAddon(noise); p5.registerAddon(random); @@ -128,18 +142,20 @@ export default function(p5, fn){ p5.registerAddon(vector); } ``` + ```js // src/math/calculation.js (redacted) -function calculation(p5, fn){ +function calculation(p5, fn) { fn.abs = Math.abs; } export default calculation; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { calculation(p5, p5.prototype); } ``` + ```js // src/app.js (redacted) // math (include if to be bundled as part of p5.js) @@ -154,6 +170,7 @@ import './math/trigonometry'; Please see relevant examples in the exploration fork for implementation. #### ESM + ESM or ES Module is the current standard in JavaScript for working with modular JavaScript code. ESM are now very widely supported with all major browsers natively supporting it, all modern build tools supports or are even built around it, and Node.js have native support for it as well. p5.js 1.0's code is already written with ESM and transpiled into a UMD module. As part of the refactor mentioned in a previos section, the syntax of the internal use of ESM will be updated to match semantic usage. The main goal will be to limit cross dependencies between modules and minimize the use of side effects imports. ```js @@ -163,7 +180,7 @@ import math from 'p5/math'; p5.registerAddon(math); // The same instance mode syntax -const sketch = (p => { +const sketch = p => { p.setup = () => { p.createCanvas(400, 400); console.log(p.ceil(2.1)); @@ -173,7 +190,7 @@ const sketch = (p => { p.background(200); p.circle(200, 200, 100); }; -}); +}; new p5(sketch); ``` @@ -190,71 +207,79 @@ Across both formats above, one thing that was not elaborated on is the static me --- ### Libraries + Existing libraries should have a level of compatibility or require minimal updates to work with p5.js 2.0. A new method of authoring libraries will be introduced that is more ergonomic. This will be through a factory function that exposes reasonable interfaces for completing the following tasks as necessary: -* Attaching methods and properties to prototype -* Lifecycle hooks -* Extending internal functionalities (eg. adding a new renderer) + +- Attaching methods and properties to prototype +- Lifecycle hooks +- Extending internal functionalities (eg. adding a new renderer) As reference, Day.js provide plugin interface in the following way: + ```js export default (option, dayjsClass, dayjsFactory) => { // extend dayjs() // e.g. add dayjs().isSameOrBefore() - dayjsClass.prototype.isSameOrBefore = function(arguments) {} + dayjsClass.prototype.isSameOrBefore = function (arguments) {}; // extend dayjs // e.g. add dayjs.utc() - dayjsFactory.utc = arguments => {} + dayjsFactory.utc = arguments => {}; // overriding existing API // e.g. extend dayjs().format() - const oldFormat = dayjsClass.prototype.format - dayjsClass.prototype.format = function(arguments) { + const oldFormat = dayjsClass.prototype.format; + dayjsClass.prototype.format = function (arguments) { // original format result - const result = oldFormat.bind(this)(arguments) + const result = oldFormat.bind(this)(arguments); // return modified result - } -} + }; +}; ``` + And is used with: + ```js dayjs.extend(myPlugin); ``` While jQuery provides the following interface: + ```js -$.fn.greenify = function() { +$.fn.greenify = function () { this.css('color', 'green'); }; $('a').greenify(); ``` + `fn` above is just an alias to `prototype` which in essense makes jQuery's plugin system identical to what p5.js does. p5.js plugins have some properties that are not present in the Day.js or jQuery use case. With Day.js, plugins are expected to be explicitly provided through `dayjs.extend()` while p5.js addons should have the expectations of being available immediately upon inclusion/load. jQuery plugin don't need to content with lifecycle hooks or other non-class instance related features. A p5.js addon should also have the flexibility of being imported as a ES module or included through a script tag, ie. there should be a ES module version and a UMD version ideally. The proposed interface that a p5.js 2.0 plugin can have is as the following: + ```js -(function(p5){ +(function (p5) { p5.registerAddon((p5, fn, lifecycles) => { // `fn` being the prototype - fn.myMethod = function(){ + fn.myMethod = function () { // Perform some tasks }; // Instead of requiring register preload, // async/await is preferred instead. - fn.loadMyData = async function(){ + fn.loadMyData = async function () { // Load some data asynchronously }; - lifecycles.presetup = function(){ + lifecycles.presetup = function () { // Run actions before `setup()` runs }; - lifecycles.postdraw = function(){ + lifecycles.postdraw = function () { // Run actions after `draw()` runs }; }); @@ -264,6 +289,7 @@ The proposed interface that a p5.js 2.0 plugin can have is as the following: --- ### Renderers + p5.js 1.0 is bundled with two renderers: 2D and WebGL. They corresponds to the HTML Canvas `2d` and `webgl` context respectively. However, there had been requests over the years to add additional renderers such as an SVG renderer or a renderer with scene graph capabilities. As the web evolve, we are also seeing new a possible standard renderer being developed, ie. [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API). As currently implemented, adding a new renderer to p5.js is not an easy task which involves many parts that expects to behave differently depending on whether the current sketch is in 2D or WebGL mode. @@ -275,11 +301,13 @@ createCanvas(400, 400, WEBGL); The constant value to determine whether a canvas is in 2D or WEBGL mode is also not easily extendable by addon libraries. With p5.js 2.0, the renderer system is redesigned and tweaked with a few key points. + 1. `p5.Renderer` class which both `p5.Renderer2D` and `p5.RendererGL` classes inherit from will now act more like an abstract class that it is meant to be. - * The `p5.Renderer` class will determine a set of basic properties and methods any renderer class inheriting from it should implement, while extra functionalities can still be implemented on top. (eg. all renderers should implement the `ellipse()` method but the WebGL renderer will also implement a `sphere()` method that 2D renderers don't need). - * The `p5.Renderer` class should never be instantiated directly. + - The `p5.Renderer` class will determine a set of basic properties and methods any renderer class inheriting from it should implement, while extra functionalities can still be implemented on top. (eg. all renderers should implement the `ellipse()` method but the WebGL renderer will also implement a `sphere()` method that 2D renderers don't need). + - The `p5.Renderer` class should never be instantiated directly. 2. The core will not have implicit knowledge of what renderers are available. Previously the two modes supported (`P2D` and `WEBGL`) are harded coded into functions like `createCanvas()` making creating a new rendering mode difficult without also modifying core functionalities. - * A list of renderers should be kept under the `p5.renderers` object with value being the class object of the renderer (that inherits from `p5.Renderer` class). + - A list of renderers should be kept under the `p5.renderers` object with value being the class object of the renderer (that inherits from `p5.Renderer` class). + ```js p5.renderers = { [constants.P2D]: Renderer2D, @@ -290,7 +318,7 @@ p5.renderers = { For an addon library to create a new renderer to work with p5, it will need to first create a class that inherits and implements the `p5.Renderer` abstract class, then register the renderer under the `p5.renderers` object. ```js -(function(p5){ +(function (p5) { class MyRenderer extends p5.Renderer { ellipse(x, y, w, h) { // ... @@ -308,7 +336,7 @@ For an addon library to create a new renderer to work with p5, it will need to f When a sketch author wants to use the addon provided renderer above, they can use the following code when creating a canvas. ```js -function setup(){ +function setup() { createCanvas(400, 400, 'myRenderer'); } ``` @@ -316,18 +344,21 @@ function setup(){ For usage that are more similar to p5.js' own renderers, constants registration can be exposed to addon library authors as well. In this case, it is recommended to make the constant value a `Symbol` matching behavior in the core library itself. #### Core question + Part of the motivation for this proposal is to enable a leaner build of the library for users who only use the 2D renderer but not the WebGL renderer and vice versa. If someone uses only the 2D canvas, there is no need for most if not all of the WebGL components to be included. This creates a question, should p5.js still be bundled with both renderers for distribution? What about new renderers in the future? There are a few options for this: -* Bundling both 2D and WebGL renderers, bundling also any new renderers if they are included in the library directly. -* Bundle only 2D renderer. Log a warning message if the sketch author tries to create a WebGL canvas without loading in the WebGL renderer as an addon. -* Bundle no renderer. All and any renderers should be included as addons. + +- Bundling both 2D and WebGL renderers, bundling also any new renderers if they are included in the library directly. +- Bundle only 2D renderer. Log a warning message if the sketch author tries to create a WebGL canvas without loading in the WebGL renderer as an addon. +- Bundle no renderer. All and any renderers should be included as addons. The third options is probably too extreme and probably should not be considered. Either of the first two options are open for discussions. --- ### Async + Data loading functions will all be updated to be fully async using promises (with `async`/`await`) while keeping the callback syntax if necessary. `setup()` will be awaited in the runtime and if the user define it as `async` functions, the promisified data loading functions can be awaited in them. With an async `setup()` function, the `draw()` loop will only start after the `setup()` function has resolved. `draw()` can be defined as an `async` function as well, although this is not recommended because of the possible timing conflict it may have with `requestAnimationFrame`. @@ -337,7 +368,9 @@ The async `setup()` function eliminates the need to have the `preload()` functio --- ### Algorithm changes + #### RNG + Seeded random number generator (RNG) will use xorshift128+. The current algorithm for RNG used in p5.js is an version of a Linear Congruential Generator (LCG). The main reasoning for this change is for performance. xorshift128+ performs better than LCG and it is the internal implementation of most of the current major browsers for `Math.random()`. The random property of xorshift128+ is similar if not better than LCG. @@ -347,6 +380,7 @@ This will be a breaking change as existing seeded RNG will not give the same ran Example implementation of a xorshift128+ backed RNG with compatible API to p5.js can be found [here](https://github.com/limzykenneth/js-xorshift128p). #### Noise + `noise()` function will use [Simplex noise](https://en.wikipedia.org/wiki/Simplex_noise) instead of the current [Perlin noise](https://en.wikipedia.org/wiki/Perlin_noise). Simplex noise has better performance than Perlin noise and no noticeable directional artifacts. Until January 8, 2022, Simplex noise is protected by a US Patent, making adopting it problematic in certain cases. The patent has since expired, opening the way for its implementation in p5.js. This will be a breaking change as existing seeded `noise()` output will not give the same output after the algorithm has switched to use Simplex noise. Consideration can be made to create a compatibility addon library that patch seeded `noise()` to use Perlin noise if necessary. @@ -354,36 +388,44 @@ Until January 8, 2022, Simplex noise is protected by a US Patent, making adoptin `noise()` will also accept higher dimensional input with the new implementation through accepting any number of numerical arguments passed to it (currently only accept either 2 or 3 arguments). #### Color + A new color module will be implemented. The requirements of this new color module are: -* Full compatibility with CSS color space support. -* Possibility for addon libraries to introduce additional color spaces. -* Accurate gamut mapping. -* API as close to existing implementation as possible. + +- Full compatibility with CSS color space support. +- Possibility for addon libraries to introduce additional color spaces. +- Accurate gamut mapping. +- API as close to existing implementation as possible. #### Math + A new math module will be implemented. The requirements of this new math module are: -* Unified vector and matrices support with compatible API with ml5.js. -* Explore reasonable performance optimizations through the use of specific algorithms, GPU implementation, or others. -* Possibility for addon libraries to provide own implementation (so that any external libraries can be made compatible without being implemented internally by p5.js). + +- Unified vector and matrices support with compatible API with ml5.js. +- Explore reasonable performance optimizations through the use of specific algorithms, GPU implementation, or others. +- Possibility for addon libraries to provide own implementation (so that any external libraries can be made compatible without being implemented internally by p5.js). --- ### Reference + The inline reference of p5.js while following a largely compatible syntax with JSDoc, under the hood is actually compiled with YUIDoc into a JSON file that the website can consume to render the actual reference page. While YUIDoc serves its purpose since the first reference documentation of p5.js, it is a tool that is no longer being worked on for over 6 years. In these 6 years, many issues and limitations means that p5.js is tied in some ways to how YUIDoc expects documentation to be authored. Several issues with setting up a development environment has also previously been tied to YUIDoc getting outdated with the overall JS ecosystem. While YUIDoc has a very similar syntax with JSDoc, there are subtle differences. For p5.js 2.0, we will move inline reference authoring to use the JSDoc syntax and generation/compilation to use [Documentation.js](https://github.com/documentationjs/documentation). #### Syntax + JSDoc syntax has for the most part settled into being a de facto standard for documentation authoring in the JavaScript ecosystem, in fact the current inline reference of p5.js is actually full compatible with JSDoc, in that it successfully compiles with JSDoc with no errors. This does not mean documentation generated through JSDoc will have the correct structure, only that it is valid syntax. Depending on the next step and the structure of what the website might expect from the reference data file, there will likely need to be minor changes to the reference throughout the codebase. #### Compilation + For compililng the inline reference into data file that can be used by the website to generate the reference pages, instead of using JSDoc, Documentation.js will be used. The reason to prefer Documentation.js over JSDoc in mainly in terms of build tool ergonomics. JSDoc is designed as a site generator with its ability to generate JSON output more of a debugging functionality instead of an intended feature; Documentation.js however supports generating JSON data files as its primary output. Documentation.js does not have as active a maintenance history as JSDoc which is a potential downside. However, since it fully supports JSDoc syntax and JSDoc syntax having widespread ubiquity, in the future if need be the transition to another tool (even JSDoc) should not require the same effort as transitioning from YUIDoc to JSDoc since the inline reference will stay the same still. #### Typing + A benefit fully adopting JSDoc syntax is that it enables direct generation of Typescript type declarations (and type checks if desired) from the JSDoc definitions. This is similar to how the source code of [SvelteKit](https://github.com/sveltejs/kit) is authored using just JavaScript while keeping type generation and type checks. This feature however is a nice to have. p5.js will aim to follow typing rules as much as possible but will not impose it as a strict requirement for contributors. Type declaration generation will also not be brought into the repo itself (including type checks) which also means types won't be published officially. @@ -391,47 +433,55 @@ This feature however is a nice to have. p5.js will aim to follow typing rules as --- ### Typography + The typography module can benefit from an overall refactor or even rewrite. The aim should be to leverage native browser/CSS capabilities as much as possible and limit the use of external library (ie. opentype.js) to only things not achievable otherwise. --- ### Misc + These are a collection of minor modifications that don't necessarily have enough scope to be a full proposal on its own. -* Remove some internal aliases to Web API. Their API references can be kept. -* Retire `p5.Table` for more semantic JavaScript data structure. -* Turn all constants that don't rely on underlying value to use `Symbol`. -* Remove support for JSONP in `loadJSON` function. - * This feature adds complexity and bypasses security feature. - * If a server was able to serve JSONP, it should be able to serve CORS resources instead. - * If required, JSONP functionality can be readded through an addon but it should not be part of p5.js core. -* Regression fixes - * FES should not be using `eval()` partly because it is not compatible with Rollup's bundling + +- Remove some internal aliases to Web API. Their API references can be kept. +- Retire `p5.Table` for more semantic JavaScript data structure. +- Turn all constants that don't rely on underlying value to use `Symbol`. +- Remove support for JSONP in `loadJSON` function. + - This feature adds complexity and bypasses security feature. + - If a server was able to serve JSONP, it should be able to serve CORS resources instead. + - If required, JSONP functionality can be readded through an addon but it should not be part of p5.js core. +- Regression fixes + - FES should not be using `eval()` partly because it is not compatible with Rollup's bundling --- ## Future + The new architecture design should aim for as much pluggability as possible, enabling additional features to be changed, added, or extended through addon libraries. Some of the possible future features that may be implemented are listed below along with reasons they are and are not targeted for immediate implementation. -* [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas) - * Provides a possibility of rendering `p5.Graphics` in web worker, potentially offloading frequent drawing of many graphics into multi-threaded like environment. - * Considered using `creatElement('canvas')` then `transferControlToOffscreen()` (and other methods) to move a `p5.Graphics` canvas to the web worker. The offscreen canvas can then be drawn onto the on screen canvas with `drawImage()`. - * WebGL context not currently supported in Safari. - * Firefox currently does not support drawing offscreen canvas onto on screen canvas. Alternative of `transferToImageBitmap()` is too slow to achieve real time performance. + +- [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas) + - Provides a possibility of rendering `p5.Graphics` in web worker, potentially offloading frequent drawing of many graphics into multi-threaded like environment. + - Considered using `creatElement('canvas')` then `transferControlToOffscreen()` (and other methods) to move a `p5.Graphics` canvas to the web worker. The offscreen canvas can then be drawn onto the on screen canvas with `drawImage()`. + - WebGL context not currently supported in Safari. + - Firefox currently does not support drawing offscreen canvas onto on screen canvas. Alternative of `transferToImageBitmap()` is too slow to achieve real time performance. ### p5.js 1.0 + The 1.x version of p5.js will continue to receive bug and critical fixes for another 12 months after the release of p5.js 2.0. No new features or feature updates will be accepted to 1.x version of p5.js and new releases will be created only when as necessary. Any existing code or addon libraries should aim to migrate to support p5.js 2.0 where possible. ## Conclusion + All the above ideas for p5.js 2.0 would not be possible without all the people who has worked on p5.js over the years, all the contributors who provided so much insight and ideas that greatly inspired this project, the many different open source projects and their contributors that directly or indirectly enabled p5.js to be the project that it is, and many many more. We welcome you to participate in this process of realizing p5.js 2.0 and to continue contributing to p5.js in the future as well! ## References -* https://github.com/AndreasMadsen/xorshift -* https://learn.jquery.com/plugins/basic-plugin-creation/ -* https://day.js.org/docs/en/plugin/plugin -* https://developer.mozilla.org/en-US/docs/Web/CSS/color_value -* https://colorjs.io/docs/gamut-mapping -* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol -* https://rollupjs.org/configuration-options/ -* https://vitejs.dev/guide/ -* https://vitest.dev/guide/browser.html + +- https://github.com/AndreasMadsen/xorshift +- https://learn.jquery.com/plugins/basic-plugin-creation/ +- https://day.js.org/docs/en/plugin/plugin +- https://developer.mozilla.org/en-US/docs/Web/CSS/color_value +- https://colorjs.io/docs/gamut-mapping +- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol +- https://rollupjs.org/configuration-options/ +- https://vitejs.dev/guide/ +- https://vitest.dev/guide/browser.html diff --git a/rolldown.config.js b/rolldown.config.js new file mode 100644 index 0000000000..3bc9e19d88 --- /dev/null +++ b/rolldown.config.js @@ -0,0 +1,95 @@ +import { defineConfig } from 'rolldown'; +import { replacePlugin } from 'rolldown/plugins'; +import { string } from 'rollup-plugin-string'; +import pkg from './package.json' with { type: 'json' }; +import { globSync } from 'glob'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import alias from '@rollup/plugin-alias'; +// import { bundleAnalyzerPlugin } from 'rolldown/experimental'; + +const plugins = [ + string({ + include: 'src/webgl/shaders/**/*' + }), + replacePlugin( + { + VERSION_WILL_BE_REPLACED_BY_BUILD: pkg.version + }, + { + preventAssignment: true + } + ) +]; +const banner = `/*! p5.js v${pkg.version} ${new Intl.DateTimeFormat('en-US', { month: 'long', day: 'numeric', year: 'numeric' }).format(new Date())} */`; +export default defineConfig([ + { + input: 'src/app.js', + output: [ + { + file: './lib/p5.js', + format: 'iife', + name: 'p5', + banner + }, + { + file: './lib/p5.esm.js', + format: 'esm', + banner + }, + { + file: './lib/p5.esm.min.js', + format: 'esm', + banner, + minify: true + } + ], + plugins + }, + { + input: 'src/app.js', + output: { + file: './lib/p5.min.js', + format: 'iife', + name: 'p5', + banner, + minify: true + }, + plugins: [ + ...plugins, + alias({ + entries: [ + { find: './core/friendly_errors', replacement: './core/noop' } + ] + }), + replacePlugin({ + IS_MINIFIED: true + }) + // bundleAnalyzerPlugin({ + // format: 'md' + // }) + ] + }, + //// ESM source build //// + { + input: Object.fromEntries( + globSync('src/**/*.js').map(file => [ + // This removes `src/` as well as the file extension from each + // file, so e.g. src/nested/foo.js becomes nested/foo + path.relative( + 'src', + file.slice(0, file.length - path.extname(file).length) + ), + // This expands the relative paths to absolute paths, so e.g. + // src/nested/foo becomes /project/src/nested/foo.js + fileURLToPath(new URL(file, import.meta.url)) + ]) + ), + output: { + format: 'es', + dir: 'dist' + }, + external: /node_modules\/(?!gifenc)/, + plugins + } +]); diff --git a/rollup.config.mjs b/rollup.config.mjs deleted file mode 100644 index 208499561c..0000000000 --- a/rollup.config.mjs +++ /dev/null @@ -1,200 +0,0 @@ -import { nodeResolve } from '@rollup/plugin-node-resolve'; -import json from '@rollup/plugin-json'; -import { string } from 'rollup-plugin-string'; -import commonjs from '@rollup/plugin-commonjs'; -import terser from '@rollup/plugin-terser'; -import pkg from './package.json' with { type: 'json' }; -import { visualizer } from 'rollup-plugin-visualizer'; -import replace from '@rollup/plugin-replace'; -import alias from '@rollup/plugin-alias'; -import { globSync } from 'glob'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { rmSync } from 'node:fs'; - -const plugins = [ - commonjs(), - nodeResolve(), - json(), - string({ - include: 'src/webgl/shaders/**/*' - }), - replace({ - values: { - 'VERSION_WILL_BE_REPLACED_BY_BUILD': pkg.version - }, - preventAssignment: true - }) -]; -const banner = `/*! p5.js v${pkg.version} ${new Intl.DateTimeFormat('en-US', { month: 'long', day: 'numeric', year: 'numeric' }).format(new Date())} */`; -const bundleSize = (name, sourcemap) => { - return visualizer({ - filename: `analyzer/${name}.html`, - gzipSize: true, - brotliSize: true, - sourcemap - }); -}; - -const modules = ['webgpu']; // TODO: also generate math build -const generateModuleBuild = () => { - return modules.map(module => { - return { - input: `src/${module}/index.js`, - output: [ - { - file: `./lib/p5.${module}.js`, - format: 'iife', - plugins: [ - bundleSize(`p5.${module}.js`) - ] - }, - { - file: `./lib/p5.${module}.min.js`, - format: 'iife', - sourcemap: 'hidden', - plugins: [ - terser({ - compress: { - global_defs: { - IS_MINIFIED: true - } - }, - format: { - comments: false - } - }), - bundleSize(`p5.${module}.min.js`) - ] - }, - { - file: `./lib/p5.${module}.esm.js`, - format: 'esm', - plugins: [ - bundleSize(`p5.${module}.esm.js`) - ] - } - ], - external: ['../core/main'], - plugins: [ - ...plugins - ] - }; - }); -}; - -rmSync('./dist', { - force: true, - recursive: true -}); - -export default [ - //// Library builds (IIFE and ESM) //// - { - input: 'src/app.js', - output: [ - { - file: './lib/p5.js', - format: 'iife', - name: 'p5', - banner, - plugins: [ - bundleSize('p5.js') - ] - }, - { - file: './lib/p5.esm.js', - format: 'esm', - banner, - plugins: [ - bundleSize('p5.esm.js') - ] - }, - { - file: './lib/p5.esm.min.js', - format: 'esm', - banner, - sourcemap: 'hidden', - plugins: [ - terser({ - compress: { - global_defs: { - IS_MINIFIED: true - } - }, - format: { - comments: false - } - }), - bundleSize('p5.esm.min.js', true) - ] - } - ], - treeshake: { - preset: 'smallest' - }, - plugins: [ - ...plugins - ] - }, - //// Minified build //// - { - input: 'src/app.js', - output: [ - { - file: './lib/p5.min.js', - format: 'iife', - name: 'p5', - banner, - sourcemap: 'hidden', - plugins: [ - terser({ - compress: { - global_defs: { - IS_MINIFIED: true - } - }, - format: { - comments: false - } - }), - bundleSize('p5.min.js', true) - ] - } - ], - treeshake: { - preset: 'smallest' - }, - plugins: [ - alias({ - entries: [ - { find: './core/friendly_errors', replacement: './core/noop' } - ] - }), - ...plugins - ] - }, - //// ESM source build //// - { - input: Object.fromEntries( - globSync('src/**/*.js').map(file => [ - // This removes `src/` as well as the file extension from each - // file, so e.g. src/nested/foo.js becomes nested/foo - path.relative( - 'src', - file.slice(0, file.length - path.extname(file).length) - ), - // This expands the relative paths to absolute paths, so e.g. - // src/nested/foo becomes /project/src/nested/foo.js - fileURLToPath(new URL(file, import.meta.url)) - ]) - ), - output: { - format: 'es', - dir: 'dist' - }, - external: /node_modules\/(?!gifenc)/, - plugins - }, - ...generateModuleBuild() -]; diff --git a/src/README.md b/src/README.md index e6b430609d..d9af00fd0b 100644 --- a/src/README.md +++ b/src/README.md @@ -4,7 +4,7 @@ The p5.js source code is organized into subdirectories which are roughly concept The `src/core` directory is the primary exception to this. It contains most of the internal logic of p5.js — the code which orchestrates everything else. It is possible to create optimized minimalist [custom builds](/contributor_docs/custom_p5_build.md) of the p5.js library which only include specific desired modules. In such a custom build, the contents of the core directory would be the only hard requirement, and everything else would be optional. -[JSDoc](https://jsdoc.app/) [module](https://jsdoc.app/tags-module.html) annotations are used throughout the codebase, but they primarily organize the public-facing [p5 reference manual](https://p5js.org/reference/), which is built automatically from the source code. These annotations structure the *expressive and creative API* of p5, and consequently they might not always align exactly with the *source code structure*. +[JSDoc](https://jsdoc.app/) [module](https://jsdoc.app/tags-module.html) annotations are used throughout the codebase, but they primarily organize the public-facing [p5 reference manual](https://p5js.org/reference/), which is built automatically from the source code. These annotations structure the _expressive and creative API_ of p5, and consequently they might not always align exactly with the _source code structure_. The `app.js` file is simply an index of all the other modules which exports the p5 constructor function. @@ -24,4 +24,4 @@ Native browser functionality is also often aliased with properties on the `p5` o ## Internal -Internal APIs which are not exposed to the user but are used for coordination within the library should generally take the form of constructor functions which can be exported across module boundaries. Those constructors are often attached to the `p5` constructor as a form of namespacing, but otherwise do not meaningfully act as methods that reference the host object. In cases where a `p5` object is needed inside a constructor, an instance will be explicitly passed as an input argument. \ No newline at end of file +Internal APIs which are not exposed to the user but are used for coordination within the library should generally take the form of constructor functions which can be exported across module boundaries. Those constructors are often attached to the `p5` constructor as a form of namespacing, but otherwise do not meaningfully act as methods that reference the host object. In cases where a `p5` object is needed inside a constructor, an instance will be explicitly passed as an input argument. diff --git a/src/accessibility/color_namer.js b/src/accessibility/color_namer.js index 9a9225c2eb..c4e36f6b1d 100644 --- a/src/accessibility/color_namer.js +++ b/src/accessibility/color_namer.js @@ -6,7 +6,7 @@ import color_conversion from '../color/color_conversion'; -function colorNamer(p5, fn){ +function colorNamer(p5, fn) { //stores the original hsb values let originalHSB; @@ -702,7 +702,7 @@ function colorNamer(p5, fn){ } //gets rgba and returs a color name - fn._rgbColorName = function(arg) { + fn._rgbColorName = function (arg) { //conversts rgba to hsb let hsb = color_conversion._rgbaToHSBA(arg); //stores hsb in global variable @@ -714,6 +714,6 @@ function colorNamer(p5, fn){ export default colorNamer; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { colorNamer(p5, p5.prototype); } diff --git a/src/accessibility/describe.js b/src/accessibility/describe.js index 8310fda3bf..1d5511cbdf 100644 --- a/src/accessibility/describe.js +++ b/src/accessibility/describe.js @@ -4,7 +4,7 @@ * @for p5 */ -function describe(p5, fn){ +function describe(p5, fn) { const descContainer = '_Description'; //Fallback container const fallbackDescId = '_fallbackDesc'; //Fallback description const fallbackTableId = '_fallbackTable'; //Fallback Table @@ -110,7 +110,7 @@ function describe(p5, fn){ * describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`, LABEL); * } */ - fn.describe = function(text, display) { + fn.describe = function (text, display) { // p5._validateParameters('describe', arguments); if (typeof text !== 'string') { return; @@ -229,7 +229,7 @@ function describe(p5, fn){ * } */ - fn.describeElement = function(name, text, display) { + fn.describeElement = function (name, text, display) { // p5._validateParameters('describeElement', arguments); if (typeof text !== 'string' || typeof name !== 'string') { return; @@ -313,7 +313,7 @@ function describe(p5, fn){ */ //creates HTML structure for canvas descriptions - fn._describeHTML = function(type, text) { + fn._describeHTML = function (type, text) { const cnvId = this.canvas.id; if (type === 'fallback') { //if there is no description container @@ -401,7 +401,7 @@ function describe(p5, fn){ } //creates HTML structure for element descriptions - fn._describeElementHTML = function(type, name, text) { + fn._describeElementHTML = function (type, name, text) { const cnvId = this.canvas.id; if (type === 'fallback') { //if there is no description container @@ -487,6 +487,6 @@ function describe(p5, fn){ export default describe; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { describe(p5, p5.prototype); } diff --git a/src/accessibility/gridOutput.js b/src/accessibility/gridOutput.js index fc3e67ed78..512d7e05ee 100644 --- a/src/accessibility/gridOutput.js +++ b/src/accessibility/gridOutput.js @@ -4,11 +4,11 @@ * @for p5 */ -function gridOutput(p5, fn){ +function gridOutput(p5, fn) { //the functions in this file support updating the grid output //updates gridOutput - fn._updateGridOutput = function(idT) { + fn._updateGridOutput = function (idT) { if (this._renderer && this._renderer.isP3D) { if (!this._didOutputGridWebGLMessage) { this._didOutputGridWebGLMessage = true; @@ -70,10 +70,10 @@ function gridOutput(p5, fn){ } // Check if shape is in canvas, skip if not - if( + if ( ingredients[x][y].loc.locY < cells.length && ingredients[x][y].loc.locX < cells[ingredients[x][y].loc.locY].length - ){ + ) { //if empty cell of location of shape is undefined if (!cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX]) { //fill it with shape info @@ -161,6 +161,6 @@ function gridOutput(p5, fn){ export default gridOutput; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { gridOutput(p5, p5.prototype); } diff --git a/src/accessibility/index.js b/src/accessibility/index.js index 4469b4fdef..e9d659e596 100644 --- a/src/accessibility/index.js +++ b/src/accessibility/index.js @@ -4,7 +4,7 @@ import textOutput from './textOutput.js'; import outputs from './outputs.js'; import colorNamer from './color_namer.js'; -export default function(p5){ +export default function (p5) { p5.registerAddon(describe); p5.registerAddon(gridOutput); p5.registerAddon(textOutput); diff --git a/src/accessibility/outputs.js b/src/accessibility/outputs.js index 5fd5183016..cffcd19165 100644 --- a/src/accessibility/outputs.js +++ b/src/accessibility/outputs.js @@ -4,7 +4,7 @@ * @for p5 */ -function outputs(p5, fn){ +function outputs(p5, fn) { /** * Creates a screen reader-accessible description of shapes on the canvas. * @@ -114,7 +114,7 @@ function outputs(p5, fn){ * describe('A red circle moves from left to right above a blue square.'); * } */ - fn.textOutput = function(display) { + fn.textOutput = function (display) { // p5._validateParameters('textOutput', arguments); //if textOutput is already true if (this._accessibleOutputs.text) { @@ -243,7 +243,7 @@ function outputs(p5, fn){ * describe('A red circle moves from left to right above a blue square.'); * } */ - fn.gridOutput = function(display) { + fn.gridOutput = function (display) { // p5._validateParameters('gridOutput', arguments); //if gridOutput is already true if (this._accessibleOutputs.grid) { @@ -263,7 +263,7 @@ function outputs(p5, fn){ }; //helper function returns true when accessible outputs are true - fn._addAccsOutput = function() { + fn._addAccsOutput = function () { //if there are no accessible outputs create object with all false if (!this._accessibleOutputs) { this._accessibleOutputs = { @@ -277,7 +277,7 @@ function outputs(p5, fn){ }; //helper function that creates html structure for accessible outputs - fn._createOutput = function(type, display) { + fn._createOutput = function (type, display) { let cnvId = this.canvas.id; //if there are no ingredients create object. this object stores data for the outputs if (!this.ingredients) { @@ -301,9 +301,8 @@ function outputs(p5, fn){ //if there is no canvas description (see describe() and describeElement()) if (!this.dummyDOM.querySelector(`#${cnvId}_Description`)) { //create html structure inside of canvas - this.dummyDOM.querySelector( - `#${cnvId}` - ).innerHTML = `
    `; + this.dummyDOM.querySelector(`#${cnvId}`).innerHTML = + `
    `; } else { //create html structure after canvas description container this.dummyDOM @@ -358,7 +357,9 @@ function outputs(p5, fn){ //if textOutput already exists if (this.dummyDOM.querySelector(query)) { //create gridOutput after textOutput - this.dummyDOM.querySelector(query).insertAdjacentHTML('afterend', inner); + this.dummyDOM + .querySelector(query) + .insertAdjacentHTML('afterend', inner); } else { //create output inside of container this.dummyDOM.querySelector(`#${container}`).innerHTML = inner; @@ -378,7 +379,7 @@ function outputs(p5, fn){ //this function is called at the end of setup and draw if using //accessibleOutputs and calls update functions of outputs - fn._updateAccsOutput = function() { + fn._updateAccsOutput = function () { let cnvId = this.canvas.id; //if the shapes are not the same as before if ( @@ -404,7 +405,7 @@ function outputs(p5, fn){ //helper function that resets all ingredients when background is called //and saves background color name - fn._accsBackground = function(args) { + fn._accsBackground = function (args) { //save current shapes as string in pShapes this.ingredients.pShapes = JSON.stringify(this.ingredients.shapes); this.ingredients.pBackground = this.ingredients.colors.background; @@ -418,7 +419,7 @@ function outputs(p5, fn){ }; //helper function that gets fill and stroke of shapes - fn._accsCanvasColors = function(f, args) { + fn._accsCanvasColors = function (f, args) { if (f === 'fill') { //update fill different if (this.ingredients.colors.fillRGBA !== args) { @@ -435,7 +436,7 @@ function outputs(p5, fn){ }; //builds ingredients.shapes used for building outputs - fn._accsOutput = function(f, args) { + fn._accsOutput = function (f, args) { if (f === 'ellipse' && args[2] === args[3]) { f = 'circle'; } else if (f === 'rectangle' && args[2] === args[3]) { @@ -480,7 +481,7 @@ function outputs(p5, fn){ if (!this.ingredients.shapes[f]) { this.ingredients.shapes[f] = [include]; //if other shapes of this type have been created - } else{ + } else { //for every shape of this type for (let y in this.ingredients.shapes[f]) { //compare it with current shape and if it already exists make add false @@ -528,8 +529,9 @@ function outputs(p5, fn){ //gets position of shape in the canvas fn._getPos = function (x, y) { - const { x: transformedX, y: transformedY } = - this.worldToScreen(new p5.Vector(x, y)); + const { x: transformedX, y: transformedY } = this.worldToScreen( + new p5.Vector(x, y) + ); const canvasWidth = this.width; const canvasHeight = this.height; if (transformedX < 0.4 * canvasWidth) { @@ -563,8 +565,8 @@ function outputs(p5, fn){ function _canvasLocator(args, canvasWidth, canvasHeight) { const noRows = 10; const noCols = 10; - let locX = Math.floor(args[0] / canvasWidth * noRows); - let locY = Math.floor(args[1] / canvasHeight * noCols); + let locX = Math.floor((args[0] / canvasWidth) * noRows); + let locY = Math.floor((args[1] / canvasHeight) * noCols); if (locX === noRows) { locX = locX - 1; } @@ -585,22 +587,22 @@ function outputs(p5, fn){ // therefore, area of arc = difference bet. arc's start and end radians * horizontal radius * vertical radius. // the below expression is adjusted for negative values and differences in arc's start and end radians over PI*2 const arcSizeInRadians = - ((shapeArgs[5] - shapeArgs[4]) % (Math.PI * 2) + Math.PI * 2) % + (((shapeArgs[5] - shapeArgs[4]) % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2); - objectArea = arcSizeInRadians * shapeArgs[2] * shapeArgs[3] / 8; + objectArea = (arcSizeInRadians * shapeArgs[2] * shapeArgs[3]) / 8; if (shapeArgs[6] === 'open' || shapeArgs[6] === 'chord') { // when the arc's mode is OPEN or CHORD, we need to account for the area of the triangle that is formed to close the arc // (Ax( By − Cy) + Bx(Cy − Ay) + Cx(Ay − By ) )/2 const Ax = shapeArgs[0]; const Ay = shapeArgs[1]; const Bx = - shapeArgs[0] + shapeArgs[2] / 2 * Math.cos(shapeArgs[4]).toFixed(2); + shapeArgs[0] + (shapeArgs[2] / 2) * Math.cos(shapeArgs[4]).toFixed(2); const By = - shapeArgs[1] + shapeArgs[3] / 2 * Math.sin(shapeArgs[4]).toFixed(2); + shapeArgs[1] + (shapeArgs[3] / 2) * Math.sin(shapeArgs[4]).toFixed(2); const Cx = - shapeArgs[0] + shapeArgs[2] / 2 * Math.cos(shapeArgs[5]).toFixed(2); + shapeArgs[0] + (shapeArgs[2] / 2) * Math.cos(shapeArgs[5]).toFixed(2); const Cy = - shapeArgs[1] + shapeArgs[3] / 2 * Math.sin(shapeArgs[5]).toFixed(2); + shapeArgs[1] + (shapeArgs[3] / 2) * Math.sin(shapeArgs[5]).toFixed(2); const areaOfExtraTriangle = Math.abs(Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By)) / 2; if (arcSizeInRadians > Math.PI) { @@ -610,7 +612,7 @@ function outputs(p5, fn){ } } } else if (objectType === 'ellipse' || objectType === 'circle') { - objectArea = 3.14 * shapeArgs[2] / 2 * shapeArgs[3] / 2; + objectArea = (((3.14 * shapeArgs[2]) / 2) * shapeArgs[3]) / 2; } else if (objectType === 'line') { objectArea = 0; } else if (objectType === 'point') { @@ -645,26 +647,27 @@ function outputs(p5, fn){ new DOMPoint(0, canvasHeight) ]; // Apply the inverse of the current transformations to the canvas corners - const currentTransform = this._renderer.isP3D ? - new DOMMatrix(this._renderer.uMVMatrix.mat4) : - this.drawingContext.getTransform(); + const currentTransform = this._renderer.isP3D + ? new DOMMatrix(this._renderer.uMVMatrix.mat4) + : this.drawingContext.getTransform(); const invertedTransform = currentTransform.inverse(); - const tc = canvasCorners.map( - corner => corner.matrixTransform(invertedTransform) + const tc = canvasCorners.map(corner => + corner.matrixTransform(invertedTransform) ); /* Use same shoelace formula used for quad area (above) to calculate the area of the canvas with inverted transformation applied */ - const transformedCanvasArea = Math.abs( - (tc[3].x + tc[0].x) * (tc[3].y - tc[0].y) + - (tc[0].x + tc[1].x) * (tc[0].y - tc[1].y) + - (tc[1].x + tc[2].x) * (tc[1].y - tc[2].y)+ - (tc[2].x + tc[3].x) * (tc[2].y - tc[3].y) - ) / 2; + const transformedCanvasArea = + Math.abs( + (tc[3].x + tc[0].x) * (tc[3].y - tc[0].y) + + (tc[0].x + tc[1].x) * (tc[0].y - tc[1].y) + + (tc[1].x + tc[2].x) * (tc[1].y - tc[2].y) + + (tc[2].x + tc[3].x) * (tc[2].y - tc[3].y) + ) / 2; /* Compare area of shape (minus transformations) to area of canvas with inverted transformation applied. Return percentage */ const untransformedArea = Math.round( - objectArea * 100 / (transformedCanvasArea) + (objectArea * 100) / transformedCanvasArea ); return untransformedArea; }; @@ -672,6 +675,6 @@ function outputs(p5, fn){ export default outputs; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { outputs(p5, p5.prototype); } diff --git a/src/accessibility/textOutput.js b/src/accessibility/textOutput.js index 46baa56071..41d21234c4 100644 --- a/src/accessibility/textOutput.js +++ b/src/accessibility/textOutput.js @@ -4,11 +4,11 @@ * @for p5 */ -function textOutput(p5, fn){ +function textOutput(p5, fn) { //the functions in this file support updating the text output //updates textOutput - fn._updateTextOutput = function(idT) { + fn._updateTextOutput = function (idT) { if (this._renderer && this._renderer.isP3D) { if (!this._didOutputTextWebGLMessage) { this._didOutputTextWebGLMessage = true; @@ -113,7 +113,8 @@ function textOutput(p5, fn){ } else { _line = _line + `, at ${ingredients[x][y].pos}`; if (x !== 'point') { - _line = _line + `, covering ${ingredients[x][y].area}% of the canvas`; + _line = + _line + `, covering ${ingredients[x][y].area}% of the canvas`; } _line = _line + '.'; } @@ -127,6 +128,6 @@ function textOutput(p5, fn){ export default textOutput; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { textOutput(p5, p5.prototype); } diff --git a/src/app.node.js b/src/app.node.js index ef270459b7..462c6167e8 100644 --- a/src/app.node.js +++ b/src/app.node.js @@ -57,4 +57,3 @@ import strands from './strands/p5.strands'; p5.registerAddon(strands); export default p5; - diff --git a/src/color/color_conversion.js b/src/color/color_conversion.js index 11823f077e..20b04e218c 100644 --- a/src/color/color_conversion.js +++ b/src/color/color_conversion.js @@ -23,7 +23,7 @@ p5.ColorConversion = { const val = hsba[2]; // Calculate lightness. - const li = (2 - sat) * val / 2; + const li = ((2 - sat) * val) / 2; // Convert saturation. if (li !== 0) { @@ -32,7 +32,7 @@ p5.ColorConversion = { } else if (li < 0.5) { sat = sat / (2 - sat); } else { - sat = sat * val / (2 - li * 2); + sat = (sat * val) / (2 - li * 2); } } @@ -112,7 +112,7 @@ p5.ColorConversion = { } // Convert saturation. - sat = 2 * (val - li) / val; + sat = (2 * (val - li)) / val; // Hue and alpha stay the same. return [hue, sat, val, hsla[3]]; diff --git a/src/color/color_spaces/hsb.js b/src/color/color_spaces/hsb.js index 2b3ece6619..fc10fdb3ca 100644 --- a/src/color/color_spaces/hsb.js +++ b/src/color/color_spaces/hsb.js @@ -52,7 +52,7 @@ export default new ColorSpace({ } } - return [hue / 6 * 360, sat * 100, val * 100]; + return [(hue / 6) * 360, sat * 100, val * 100]; }, toBase, @@ -61,13 +61,9 @@ export default new ColorSpace({ type: 'custom', serialize: (coords, alpha) => { const rgb = toBase(coords); - let ret = `rgb(${ - Math.round(rgb[0] * 100 * 100) / 100 - }% ${ + let ret = `rgb(${Math.round(rgb[0] * 100 * 100) / 100}% ${ Math.round(rgb[1] * 100 * 100) / 100 - }% ${ - Math.round(rgb[2] * 100 * 100) / 100 - }%`; + }% ${Math.round(rgb[2] * 100 * 100) / 100}%`; if (alpha < 1) { ret += ` / ${alpha}`; @@ -78,10 +74,10 @@ export default new ColorSpace({ return ret; } }, - 'hsb': { + hsb: { coords: [' | ', '', ''] }, - 'hsba': { + hsba: { coords: [' | ', '', ''], commans: true, lastAlpha: true @@ -89,8 +85,8 @@ export default new ColorSpace({ } }); -function toBase(hsb){ - const hue = hsb[0] / 360 * 6; // We will split hue into 6 sectors. +function toBase(hsb) { + const hue = (hsb[0] / 360) * 6; // We will split hue into 6 sectors. const sat = hsb[1] / 100; const val = hsb[2] / 100; diff --git a/src/color/creating_reading.js b/src/color/creating_reading.js index 39f16db09d..2cf40ce596 100644 --- a/src/color/creating_reading.js +++ b/src/color/creating_reading.js @@ -71,7 +71,7 @@ export const OKLCH = 'oklch'; */ export const RGBA = 'rgba'; -function creatingReading(p5, fn){ +function creatingReading(p5, fn) { fn.RGB = RGB; fn.RGBP3 = RGBP3; fn.HSB = HSB; @@ -100,7 +100,7 @@ function creatingReading(p5, fn){ [OKLAB]: [100, [-125, 125], [-125, 125], 1], [OKLCH]: [100, 150, 360, 1], - clone: function(){ + clone: function () { const cloned = { ...this }; for (const key in cloned) { if (cloned[key] instanceof Array) { @@ -403,7 +403,7 @@ function creatingReading(p5, fn){ * @param {p5.Color} color * @return {p5.Color} */ - fn.color = function(...args) { + fn.color = function (...args) { // p5._validateParameters('color', args); if (args[0] instanceof Color) { // TODO: perhaps change color mode to match instance mode? @@ -566,7 +566,7 @@ function creatingReading(p5, fn){ * color components, or CSS color string. * @return {Number} the red value. */ - fn.red = function(c) { + fn.red = function (c) { // p5._validateParameters('red', arguments); // Get current red max return this.color(c)._getRed(); @@ -719,7 +719,7 @@ function creatingReading(p5, fn){ * color components, or CSS color string. * @return {Number} the green value. */ - fn.green = function(c) { + fn.green = function (c) { // p5._validateParameters('green', arguments); // Get current green max return this.color(c)._getGreen(); @@ -872,7 +872,7 @@ function creatingReading(p5, fn){ * color components, or CSS color string. * @return {Number} the blue value. */ - fn.blue = function(c) { + fn.blue = function (c) { // p5._validateParameters('blue', arguments); // Get current blue max return this.color(c)._getBlue(); @@ -993,7 +993,7 @@ function creatingReading(p5, fn){ * color components, or CSS color string. * @return {Number} the alpha value. */ - fn.alpha = function(c) { + fn.alpha = function (c) { // p5._validateParameters('alpha', arguments); // Get current alpha max return this.color(c)._getAlpha(); @@ -1134,19 +1134,19 @@ function creatingReading(p5, fn){ * color components, or CSS color string. * @return {Number} the hue value. */ - fn.hue = function(c) { + fn.hue = function (c) { let colorMode = HSL; let i = 0; - if( + if ( this._renderer.states.colorMode === HSB || this._renderer.states.colorMode === HSL - ){ + ) { colorMode = this._renderer.states.colorMode; - }else if( + } else if ( this._renderer.states.colorMode === LCH || this._renderer.states.colorMode === OKLCH - ){ + ) { colorMode = this._renderer.states.colorMode; i = 2; } @@ -1341,8 +1341,8 @@ function creatingReading(p5, fn){ * color components, or CSS color string. * @return {Number} the saturation value */ - fn.saturation = function(c) { - const colorMode = (this._renderer.states.colorMode === HSB) ? HSB : HSL; + fn.saturation = function (c) { + const colorMode = this._renderer.states.colorMode === HSB ? HSB : HSL; return this.color(c)._getSaturation( this._renderer.states.colorMaxes[colorMode][1] ); @@ -1504,7 +1504,7 @@ function creatingReading(p5, fn){ * color components, or CSS color string. * @return {Number} the brightness value. */ - fn.brightness = function(c) { + fn.brightness = function (c) { return this.color(c)._getBrightness( this._renderer.states.colorMaxes.hsb[2] ); @@ -1666,10 +1666,8 @@ function creatingReading(p5, fn){ * color components, or CSS color string. * @return {Number} the lightness value. */ - fn.lightness = function(c) { - return this.color(c)._getLightness( - this._renderer.states.colorMaxes.hsl[2] - ); + fn.lightness = function (c) { + return this.color(c)._getLightness(this._renderer.states.colorMaxes.hsl[2]); }; /** @@ -1757,7 +1755,7 @@ function creatingReading(p5, fn){ * @param {Number} amt number between 0 and 1. * @return {p5.Color} interpolated color. */ - fn.lerpColor = function(c1, c2, amt) { + fn.lerpColor = function (c1, c2, amt) { // p5._validateParameters('lerpColor', arguments); return c1.lerp(c2, amt, this._renderer.states.colorMode); }; @@ -1793,10 +1791,9 @@ function creatingReading(p5, fn){ * ], millis() / 10000 % 1)); * } */ - fn.paletteLerp = function(color_stops, amt) { + fn.paletteLerp = function (color_stops, amt) { const first_color_stop = color_stops[0]; - if (amt < first_color_stop[1]) - return this.color(first_color_stop[0]); + if (amt < first_color_stop[1]) return this.color(first_color_stop[0]); for (let i = 1; i < color_stops.length; i++) { const color_stop = color_stops[i]; @@ -1816,6 +1813,6 @@ function creatingReading(p5, fn){ export default creatingReading; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { creatingReading(p5, p5.prototype); -} \ No newline at end of file +} diff --git a/src/color/index.js b/src/color/index.js index bd0fb96f58..935d0bc7c1 100644 --- a/src/color/index.js +++ b/src/color/index.js @@ -2,7 +2,7 @@ import creatingReading from './creating_reading.js'; import p5color from './p5.Color.js'; import setting from './setting.js'; -export default function(p5){ +export default function (p5) { p5.registerAddon(creatingReading); p5.registerAddon(p5color); p5.registerAddon(setting); diff --git a/src/color/p5.Color.culori.js b/src/color/p5.Color.culori.js index 12ce7e724d..dbbb42bd66 100644 --- a/src/color/p5.Color.culori.js +++ b/src/color/p5.Color.culori.js @@ -4,63 +4,33 @@ export class Color { mode; array; - static addColorMode(mode, definition){ + static addColorMode(mode, definition) {} - } + constructor(vals) {} - constructor(vals){ + toString() {} - } + setRed(new_red) {} - toString(){ + setGreen(new_green) {} - } + setBlue(new_blue) {} - setRed(new_red){ + setAlpha(new_alpha) {} - } + _getRed() {} - setGreen(new_green){ + _getGreen() {} - } + _getBlue() {} - setBlue(new_blue){ + _getAlpha() {} - } + _getHue() {} - setAlpha(new_alpha){ + _getSaturation() {} - } + _getBrightness() {} - _getRed(){ - - } - - _getGreen(){ - - } - - _getBlue(){ - - } - - _getAlpha(){ - - } - - _getHue(){ - - } - - _getSaturation(){ - - } - - _getBrightness(){ - - } - - _getLightness(){ - - } + _getLightness() {} } diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js index fb4b62a4f8..ea852ac80a 100644 --- a/src/color/p5.Color.js +++ b/src/color/p5.Color.js @@ -4,8 +4,17 @@ * @for p5 */ -import { RGB, RGBP3, HSL, HSB, HWB, LAB, LCH, OKLAB, OKLCH } from './creating_reading'; - +import { + RGB, + RGBP3, + HSL, + HSB, + HWB, + LAB, + LCH, + OKLAB, + OKLCH +} from './creating_reading'; import { ColorSpace, @@ -14,14 +23,11 @@ import { serialize, parse, range, - sRGB, HSL as HSLSpace, HWB as HWBSpace, - Lab, LCH as LCHSpace, - OKLab, OKLCH as OKLCHSpace, contrastWCAG21, @@ -31,29 +37,26 @@ import { import HSBSpace from './color_spaces/hsb.js'; const map = (n, start1, stop1, start2, stop2, clamp) => { - let result = ((n - start1) / (stop1 - start1) * (stop2 - start2) + start2); + let result = ((n - start1) / (stop1 - start1)) * (stop2 - start2) + start2; if (clamp) { result = Math.max(result, Math.min(start2, stop2)); result = Math.min(result, Math.max(start2, stop2)); } return result; -} +}; -const toHexComponent = (v) => { +const toHexComponent = v => { const vInt = ~~(v * 255); - const hex = vInt.toString(16) + const hex = vInt.toString(16); if (hex.length < 2) { - return '0' + hex + return '0' + hex; } else { - return hex + return hex; } -} +}; const serializationMap = new Map(); - - - class Color { static colorMap = {}; static #colorjsMaxes = {}; @@ -68,16 +71,18 @@ class Color { // Used to add additional color modes to p5.js // Uses underlying library's definition - static addColorMode(mode, definition){ + static addColorMode(mode, definition) { ColorSpace.register(definition); Color.colorMap[mode] = definition.id; // Get colorjs maxes - Color.#colorjsMaxes[mode] = Object.values(definition.coords) - .reduce((acc, v) => { + Color.#colorjsMaxes[mode] = Object.values(definition.coords).reduce( + (acc, v) => { acc.push(v.refRange || v.range); return acc; - }, []); + }, + [] + ); Color.#colorjsMaxes[mode].push([0, 1]); // Get grayscale mapping @@ -88,55 +93,60 @@ class Color { // This changes with the color object this._cachedMode = colorMode || RGB; - if(vals instanceof Color){ + if (vals instanceof Color) { // Received Color object to be used for color mode conversion - const mode = colorMode ? - Color.colorMap[colorMode] : - Color.colorMap[vals.mode]; + const mode = colorMode + ? Color.colorMap[colorMode] + : Color.colorMap[vals.mode]; this._initialize = () => { this._cachedColor = to(vals._color, mode); this._cachedMode = mode; }; - - }else if (typeof vals === 'object' && !Array.isArray(vals) && vals !== null){ + } else if ( + typeof vals === 'object' && + !Array.isArray(vals) && + vals !== null + ) { // Received color.js object to be used internally - const mode = colorMode ? - Color.colorMap[colorMode] : - vals.spaceId; + const mode = colorMode ? Color.colorMap[colorMode] : vals.spaceId; this._initialize = () => { this._cachedColor = to(vals, mode); - this._cachedMode = colorMode || Object.entries(Color.colorMap) - .find(([key, val]) => { + this._cachedMode = + colorMode || + Object.entries(Color.colorMap).find(([key, val]) => { return val === this._cachedColor.spaceId; }); }; - - } else if(typeof vals[0] === 'string') { + } else if (typeof vals[0] === 'string') { // Received string this._defaultStringValue = vals[0]; this._initialize = () => { - try{ + try { this._cachedColor = parse(vals[0]); const [mode] = Object.entries(Color.colorMap).find(([key, val]) => { return val === this._cachedColor.spaceId; }); this._cachedMode = mode; this._cachedColor = to(this._cachedColor, this._cachedColor.spaceId); - }catch(err){ + } catch (err) { // TODO: Invalid color string throw new Error('Invalid color string'); } }; - - }else{ + } else { // Received individual channel values let mappedVals; - if(colorMaxes){ + if (colorMaxes) { // NOTE: need to consider different number of arguments (eg. CMYK) - if(vals.length === 4){ - mappedVals = Color.mapColorRange(vals, this._cachedMode, colorMaxes, clamp); - }else if(vals.length === 3){ + if (vals.length === 4) { + mappedVals = Color.mapColorRange( + vals, + this._cachedMode, + colorMaxes, + clamp + ); + } else if (vals.length === 3) { mappedVals = Color.mapColorRange( [vals[0], vals[1], vals[2]], this._cachedMode, @@ -144,15 +154,15 @@ class Color { clamp ); mappedVals.push(1); - }else if(vals.length === 2){ + } else if (vals.length === 2) { // Grayscale with alpha - if(Color.#grayscaleMap[this._cachedMode]){ + if (Color.#grayscaleMap[this._cachedMode]) { mappedVals = Color.#grayscaleMap[this._cachedMode]( vals[0], colorMaxes, clamp ); - }else{ + } else { mappedVals = Color.mapColorRange( [vals[0], vals[0], vals[0]], this._cachedMode, @@ -160,28 +170,21 @@ class Color { clamp ); } - const alphaMaxes = Array.isArray(colorMaxes[colorMaxes.length-1]) ? - colorMaxes[colorMaxes.length-1] : - [0, colorMaxes[colorMaxes.length-1]]; + const alphaMaxes = Array.isArray(colorMaxes[colorMaxes.length - 1]) + ? colorMaxes[colorMaxes.length - 1] + : [0, colorMaxes[colorMaxes.length - 1]]; mappedVals.push( - map( - vals[1], - alphaMaxes[0], - alphaMaxes[1], - 0, - 1, - clamp - ) + map(vals[1], alphaMaxes[0], alphaMaxes[1], 0, 1, clamp) ); - }else if(vals.length === 1){ + } else if (vals.length === 1) { // Grayscale only - if(Color.#grayscaleMap[this._cachedMode]){ + if (Color.#grayscaleMap[this._cachedMode]) { mappedVals = Color.#grayscaleMap[this._cachedMode]( vals[0], colorMaxes, clamp ); - }else{ + } else { mappedVals = Color.mapColorRange( [vals[0], vals[0], vals[0]], this._cachedMode, @@ -190,23 +193,34 @@ class Color { ); } mappedVals.push(1); - }else{ + } else { throw new Error('Invalid color'); } - }else{ + } else { mappedVals = vals; } if (this._cachedMode === RGB) { if (mappedVals[3] === 1) { // Faster for the browser to parse than rgba - this._defaultStringValue = '#' + toHexComponent(mappedVals[0]) + toHexComponent(mappedVals[1]) + toHexComponent(mappedVals[2]); + this._defaultStringValue = + '#' + + toHexComponent(mappedVals[0]) + + toHexComponent(mappedVals[1]) + + toHexComponent(mappedVals[2]); } else { - this._defaultStringValue = '#' + toHexComponent(mappedVals[0]) + toHexComponent(mappedVals[1]) + toHexComponent(mappedVals[2]) + toHexComponent(mappedVals[3]);; + this._defaultStringValue = + '#' + + toHexComponent(mappedVals[0]) + + toHexComponent(mappedVals[1]) + + toHexComponent(mappedVals[2]) + + toHexComponent(mappedVals[3]); } } this._initialize = () => { - const space = Color.colorMap[this._cachedMode] || console.error('Invalid color mode'); + const space = + Color.colorMap[this._cachedMode] || + console.error('Invalid color mode'); const coords = mappedVals.slice(0, 3); const color = { @@ -245,11 +259,11 @@ class Color { } // Convert from p5 color range to color.js color range - static mapColorRange(origin, mode, maxes, clamp){ + static mapColorRange(origin, mode, maxes, clamp) { const p5Maxes = maxes.map(max => { - if(!Array.isArray(max)){ + if (!Array.isArray(max)) { return [0, max]; - }else{ + } else { return max; } }); @@ -258,8 +272,10 @@ class Color { return origin.map((channel, i) => { const newval = map( channel, - p5Maxes[i][0], p5Maxes[i][1], - colorjsMaxes[i][0], colorjsMaxes[i][1], + p5Maxes[i][0], + p5Maxes[i][1], + colorjsMaxes[i][0], + colorjsMaxes[i][1], clamp ); return newval; @@ -267,11 +283,11 @@ class Color { } // Convert from color.js color range to p5 color range - static unmapColorRange(origin, mode, maxes){ + static unmapColorRange(origin, mode, maxes) { const p5Maxes = maxes.map(max => { - if(!Array.isArray(max)){ + if (!Array.isArray(max)) { return [0, max]; - }else{ + } else { return max; } }); @@ -280,15 +296,17 @@ class Color { return origin.map((channel, i) => { const newval = map( channel, - colorjsMaxes[i][0], colorjsMaxes[i][1], - p5Maxes[i][0], p5Maxes[i][1] + colorjsMaxes[i][0], + colorjsMaxes[i][1], + p5Maxes[i][0], + p5Maxes[i][1] ); return newval; }); } // Will do conversion in-Gamut as out of Gamut conversion is only really useful for futher conversions - #toColorMode(mode){ + #toColorMode(mode) { return new Color(this._color, mode); } @@ -297,21 +315,19 @@ class Color { return this._getRGBA(); } - array(){ + array() { return this._array; } - lerp(color, amt, mode){ + lerp(color, amt, mode) { // Find the closest common ancestor color space let spaceIndex = -1; - while( - ( - spaceIndex+1 < this._color.space.path.length || - spaceIndex+1 < color._color.space.path.length - ) && - this._color.space.path[spaceIndex+1] === - color._color.space.path[spaceIndex+1] - ){ + while ( + (spaceIndex + 1 < this._color.space.path.length || + spaceIndex + 1 < color._color.space.path.length) && + this._color.space.path[spaceIndex + 1] === + color._color.space.path[spaceIndex + 1] + ) { spaceIndex += 1; } @@ -365,10 +381,10 @@ class Color { * describe('The text "#9932cc" written in purple on a gray background.'); * } */ - toString(format) { - if (format === undefined && this._defaultStringValue !== undefined) { + toString(format) { + if (format === undefined && this._defaultStringValue !== undefined) { return this._defaultStringValue; - } + } let outputFormat = format; if (format === '#rrggbb') { @@ -386,19 +402,19 @@ class Color { if (format === '#rrggbb') { colorString = String(colorString); if (colorString.length === 4) { - const r = colorString[1]; - const g = colorString[2]; - const b = colorString[3]; - colorString = `#${r}${r}${g}${g}${b}${b}`; + const r = colorString[1]; + const g = colorString[2]; + const b = colorString[3]; + colorString = `#${r}${r}${g}${g}${b}${b}`; } if (colorString.length > 7) { - colorString = colorString.slice(0, 7); + colorString = colorString.slice(0, 7); } colorString = colorString.toLowerCase(); } if (serializationMap.size > 1000) { - serializationMap.delete(serializationMap.keys().next().value) + serializationMap.delete(serializationMap.keys().next().value); } serializationMap.set(key, colorString); } @@ -482,10 +498,10 @@ class Color { * text(nf(contrast.APCA.value, 0, 2), 10, 85); * } */ - contrast(other_color, options='WCAG21') { - if(options !== 'all'){ + contrast(other_color, options = 'WCAG21') { + if (options !== 'all') { let contrastVal, minimum; - switch(options){ + switch (options) { case 'WCAG21': contrastVal = contrastWCAG21(this._color, other_color._color); minimum = 4.5; @@ -499,7 +515,7 @@ class Color { } return contrastVal >= minimum; - }else{ + } else { const wcag21Value = contrastWCAG21(this._color, other_color._color); const apcaValue = contrastAPCA(this._color, other_color._color); return { @@ -514,7 +530,7 @@ class Color { } }; } - }; + } /** * Sets the red component of a color. @@ -548,18 +564,18 @@ class Color { * describe('Two rectangles. The left one is salmon pink and the right one is teal.'); * } */ - setRed(new_red, max=[0, 1]) { + setRed(new_red, max = [0, 1]) { this._defaultStringValue = undefined; - if(!Array.isArray(max)){ + if (!Array.isArray(max)) { max = [0, max]; } const colorjsMax = Color.#colorjsMaxes[RGB][0]; const newval = map(new_red, max[0], max[1], colorjsMax[0], colorjsMax[1]); - if(this.mode === RGB || this.mode === RGBP3){ + if (this.mode === RGB || this.mode === RGBP3) { this._color.coords[0] = newval; - }else{ + } else { // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; const representation = to(this._color, 'srgb'); @@ -600,18 +616,18 @@ class Color { * describe('Two rectangles. The left one is salmon pink and the right one is yellow.'); * } */ - setGreen(new_green, max=[0, 1]) { + setGreen(new_green, max = [0, 1]) { this._defaultStringValue = undefined; - if(!Array.isArray(max)){ + if (!Array.isArray(max)) { max = [0, max]; } const colorjsMax = Color.#colorjsMaxes[RGB][1]; const newval = map(new_green, max[0], max[1], colorjsMax[0], colorjsMax[1]); - if(this.mode === RGB || this.mode === RGBP3){ + if (this.mode === RGB || this.mode === RGBP3) { this._color.coords[1] = newval; - }else{ + } else { // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; const representation = to(this._color, 'srgb'); @@ -652,18 +668,18 @@ class Color { * describe('Two rectangles. The left one is salmon pink and the right one is pale fuchsia.'); * } */ - setBlue(new_blue, max=[0, 1]) { + setBlue(new_blue, max = [0, 1]) { this._defaultStringValue = undefined; - if(!Array.isArray(max)){ + if (!Array.isArray(max)) { max = [0, max]; } const colorjsMax = Color.#colorjsMaxes[RGB][2]; const newval = map(new_blue, max[0], max[1], colorjsMax[0], colorjsMax[1]); - if(this.mode === RGB || this.mode === RGBP3){ + if (this.mode === RGB || this.mode === RGBP3) { this._color.coords[2] = newval; - }else{ + } else { // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; const representation = to(this._color, 'srgb'); @@ -705,9 +721,9 @@ class Color { * describe('Two rectangles. The left one is salmon pink and the right one is faded pink.'); * } */ - setAlpha(new_alpha, max=[0, 1]) { + setAlpha(new_alpha, max = [0, 1]) { this._defaultStringValue = undefined; - if(!Array.isArray(max)){ + if (!Array.isArray(max)) { max = [0, max]; } @@ -717,7 +733,7 @@ class Color { this._color.alpha = newval; } - _getRGBA(maxes=[1, 1, 1, 1]) { + _getRGBA(maxes = [1, 1, 1, 1]) { // Get colorjs maxes const colorjsMaxes = Color.#colorjsMaxes[RGB]; @@ -725,10 +741,10 @@ class Color { let coords = structuredClone(to(this._color, 'srgb').coords); coords.push(this._color.alpha); - const rangeMaxes = maxes.map((v) => { - if(!Array.isArray(v)){ + const rangeMaxes = maxes.map(v => { + if (!Array.isArray(v)) { return [0, v]; - }else{ + } else { return v; } }); @@ -736,8 +752,10 @@ class Color { coords = coords.map((coord, i) => { return map( coord, - colorjsMaxes[i][0], colorjsMaxes[i][1], - rangeMaxes[i][0], rangeMaxes[i][1] + colorjsMaxes[i][0], + colorjsMaxes[i][1], + rangeMaxes[i][0], + rangeMaxes[i][1] ); }); @@ -748,22 +766,30 @@ class Color { return this.mode; } - _getRed(max=[0, 1]) { - if(!Array.isArray(max)){ + _getRed(max = [0, 1]) { + if (!Array.isArray(max)) { max = [0, max]; } - if(this.mode === RGB || this.mode === RGBP3){ + if (this.mode === RGB || this.mode === RGBP3) { const colorjsMax = Color.#colorjsMaxes[this.mode][0]; return map( this._color.coords[0], - colorjsMax[0], colorjsMax[1], - max[0], max[1] + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] ); - }else{ + } else { // Will do an imprecise conversion to 'srgb', not recommended const colorjsMax = Color.#colorjsMaxes[RGB][0]; - return map(to(this._color, 'srgb').coords[0], colorjsMax[0], colorjsMax[1], max[0], max[1]); + return map( + to(this._color, 'srgb').coords[0], + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] + ); } } @@ -772,46 +798,62 @@ class Color { * returns it in the range 0–255 by default. When `colorMode()` is given to an * RBG value, the green value within the givin range is returned */ - _getGreen(max=[0, 1]) { - if(!Array.isArray(max)){ + _getGreen(max = [0, 1]) { + if (!Array.isArray(max)) { max = [0, max]; } - if(this.mode === RGB || this.mode === RGBP3){ + if (this.mode === RGB || this.mode === RGBP3) { const colorjsMax = Color.#colorjsMaxes[this.mode][1]; return map( this._color.coords[1], - colorjsMax[0], colorjsMax[1], - max[0], max[1] + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] ); - }else{ + } else { // Will do an imprecise conversion to 'srgb', not recommended const colorjsMax = Color.#colorjsMaxes[RGB][1]; - return map(to(this._color, 'srgb').coords[1], colorjsMax[0], colorjsMax[1], max[0], max[1]); + return map( + to(this._color, 'srgb').coords[1], + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] + ); } } - _getBlue(max=[0, 1]) { - if(!Array.isArray(max)){ + _getBlue(max = [0, 1]) { + if (!Array.isArray(max)) { max = [0, max]; } - if(this.mode === RGB || this.mode === RGBP3){ + if (this.mode === RGB || this.mode === RGBP3) { const colorjsMax = Color.#colorjsMaxes[this.mode][2]; return map( this._color.coords[2], - colorjsMax[0], colorjsMax[1], - max[0], max[1] + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] ); - }else{ + } else { // Will do an imprecise conversion to 'srgb', not recommended const colorjsMax = Color.#colorjsMaxes[RGB][2]; - return map(to(this._color, 'srgb').coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]); + return map( + to(this._color, 'srgb').coords[2], + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] + ); } } - _getAlpha(max=[0, 1]) { - if(!Array.isArray(max)){ + _getAlpha(max = [0, 1]) { + if (!Array.isArray(max)) { max = [0, max]; } @@ -825,22 +867,30 @@ class Color { * an HSB color object, but will default to the HSL-normalized saturation * otherwise. */ - _getHue(max=[0, 360]) { - if(!Array.isArray(max)){ + _getHue(max = [0, 360]) { + if (!Array.isArray(max)) { max = [0, max]; } - if(this.mode === HSB || this.mode === HSL){ + if (this.mode === HSB || this.mode === HSL) { const colorjsMax = Color.#colorjsMaxes[this.mode][0]; return map( this._color.coords[0], - colorjsMax[0], colorjsMax[1], - max[0], max[1] + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] ); - }else{ + } else { // Will do an imprecise conversion to 'HSL', not recommended const colorjsMax = Color.#colorjsMaxes[HSL][0]; - return map(to(this._color, 'hsl').coords[0], colorjsMax[0], colorjsMax[1], max[0], max[1]); + return map( + to(this._color, 'hsl').coords[0], + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] + ); } } @@ -849,22 +899,30 @@ class Color { * the HSB saturation when supplied with an HSB color object, but will default * to the HSL saturation otherwise. */ - _getSaturation(max=[0, 100]) { - if(!Array.isArray(max)){ + _getSaturation(max = [0, 100]) { + if (!Array.isArray(max)) { max = [0, max]; } - if(this.mode === HSB || this.mode === HSL){ + if (this.mode === HSB || this.mode === HSL) { const colorjsMax = Color.#colorjsMaxes[this.mode][1]; return map( this._color.coords[1], - colorjsMax[0], colorjsMax[1], - max[0], max[1] + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] ); - }else{ + } else { // Will do an imprecise conversion to 'HSL', not recommended const colorjsMax = Color.#colorjsMaxes[HSL][1]; - return map(to(this._color, 'hsl').coords[1], colorjsMax[0], colorjsMax[1], max[0], max[1]); + return map( + to(this._color, 'hsl').coords[1], + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] + ); } } @@ -875,46 +933,62 @@ class Color { * brightness value in the range. By default, this function will return * the HSB brightness within the range 0 - 100. */ - _getBrightness(max=[0, 100]) { - if(!Array.isArray(max)){ + _getBrightness(max = [0, 100]) { + if (!Array.isArray(max)) { max = [0, max]; } - if(this.mode === HSB){ + if (this.mode === HSB) { const colorjsMax = Color.#colorjsMaxes[this.mode][2]; return map( this._color.coords[2], - colorjsMax[0], colorjsMax[1], - max[0], max[1] + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] ); - }else{ + } else { // Will do an imprecise conversion to 'HSB', not recommended const colorjsMax = Color.#colorjsMaxes[HSB][2]; - return map(to(this._color, 'hsb').coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]); + return map( + to(this._color, 'hsb').coords[2], + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] + ); } } - _getLightness(max=[0, 100]) { - if(!Array.isArray(max)){ + _getLightness(max = [0, 100]) { + if (!Array.isArray(max)) { max = [0, max]; } - if(this.mode === HSL){ + if (this.mode === HSL) { const colorjsMax = Color.#colorjsMaxes[this.mode][2]; return map( this._color.coords[2], - colorjsMax[0], colorjsMax[1], - max[0], max[1] + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] ); - }else{ + } else { // Will do an imprecise conversion to 'HSL', not recommended const colorjsMax = Color.#colorjsMaxes[HSL][2]; - return map(to(this._color, 'hsl').coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]); + return map( + to(this._color, 'hsl').coords[2], + colorjsMax[0], + colorjsMax[1], + max[0], + max[1] + ); } } } -function color(p5, fn, lifecycles){ +function color(p5, fn, lifecycles) { /** * A class to describe a color. * @@ -948,12 +1022,12 @@ function color(p5, fn, lifecycles){ */ p5.Color = Color; - sRGB.fromGray = P3.fromGray = function(val, maxes, clamp){ + sRGB.fromGray = P3.fromGray = function (val, maxes, clamp) { // Use blue max const p5Maxes = maxes.map(max => { - if(!Array.isArray(max)){ + if (!Array.isArray(max)) { return [0, max]; - }else{ + } else { return max; } }); @@ -962,12 +1036,12 @@ function color(p5, fn, lifecycles){ return [v, v, v]; }; - HSBSpace.fromGray = HSLSpace.fromGray = function(val, maxes, clamp){ + HSBSpace.fromGray = HSLSpace.fromGray = function (val, maxes, clamp) { // Use brightness max const p5Maxes = maxes.map(max => { - if(!Array.isArray(max)){ + if (!Array.isArray(max)) { return [0, max]; - }else{ + } else { return max; } }); @@ -976,26 +1050,26 @@ function color(p5, fn, lifecycles){ return [0, 0, v]; }; - HWBSpace.fromGray = function(val, maxes, clamp){ + HWBSpace.fromGray = function (val, maxes, clamp) { // Use Whiteness and Blackness to create number line const p5Maxes = maxes.map(max => { - if(!Array.isArray(max)){ + if (!Array.isArray(max)) { return [0, max]; - }else{ + } else { return max; } }); const wbMax = - (Math.abs(p5Maxes[1][0] - p5Maxes[1][1])) / 2 + - (Math.abs(p5Maxes[2][0] - p5Maxes[2][1])) / 2; + Math.abs(p5Maxes[1][0] - p5Maxes[1][1]) / 2 + + Math.abs(p5Maxes[2][0] - p5Maxes[2][1]) / 2; const nVal = map(val, 0, wbMax, 0, 100); let white, black; - if(nVal < 50){ + if (nVal < 50) { black = nVal; white = 100 - nVal; - }else if(nVal >= 50){ + } else if (nVal >= 50) { white = nVal; black = 100 - nVal; } @@ -1003,22 +1077,22 @@ function color(p5, fn, lifecycles){ }; Lab.fromGray = - LCHSpace.fromGray = - OKLab.fromGray = - OKLCHSpace.fromGray = - function(val, maxes, clamp){ - // Use lightness max - const p5Maxes = maxes.map(max => { - if(!Array.isArray(max)){ - return [0, max]; - }else{ - return max; - } - }); + LCHSpace.fromGray = + OKLab.fromGray = + OKLCHSpace.fromGray = + function (val, maxes, clamp) { + // Use lightness max + const p5Maxes = maxes.map(max => { + if (!Array.isArray(max)) { + return [0, max]; + } else { + return max; + } + }); - const v = map(val, p5Maxes[0][0], p5Maxes[0][1], 0, 100, clamp); - return [v, 0, 0]; - }; + const v = map(val, p5Maxes[0][0], p5Maxes[0][1], 0, 100, clamp); + return [v, 0, 0]; + }; // Register color modes and initialize Color maxes to what p5 has set for itself p5.Color.addColorMode(RGB, sRGB); @@ -1031,35 +1105,35 @@ function color(p5, fn, lifecycles){ p5.Color.addColorMode(OKLAB, OKLab); p5.Color.addColorMode(OKLCH, OKLCHSpace); - lifecycles.presetup = function(){ + lifecycles.presetup = function () { const pInst = this; // Decorate set methods const setMethods = ['Red', 'Green', 'Blue', 'Alpha']; - for(let i in setMethods){ + for (let i in setMethods) { const method = setMethods[i]; const setCopy = p5.Color.prototype['set' + method]; - p5.Color.prototype['set' + method] = function(newval, max){ + p5.Color.prototype['set' + method] = function (newval, max) { max = max || pInst?._renderer?.states?.colorMaxes?.[RGB][i]; return setCopy.call(this, newval, max); }; } // Decorate get methods - function decorateGet(channel, modes){ + function decorateGet(channel, modes) { const getCopy = p5.Color.prototype['_get' + channel]; - p5.Color.prototype['_get' + channel] = function(max){ - if(Object.keys(modes).includes(this.mode)){ - max = max || + p5.Color.prototype['_get' + channel] = function (max) { + if (Object.keys(modes).includes(this.mode)) { + max = + max || pInst?._renderer?.states?.colorMaxes?.[this.mode][modes[this.mode]]; - }else{ + } else { const defaultMode = Object.keys(modes)[0]; - max = max || - pInst - ?._renderer - ?.states - ?.colorMaxes - ?.[defaultMode][modes[defaultMode]]; + max = + max || + pInst?._renderer?.states?.colorMaxes?.[defaultMode][ + modes[defaultMode] + ]; } return getCopy.call(this, max); @@ -1113,6 +1187,6 @@ function color(p5, fn, lifecycles){ export default color; export { Color }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { color(p5, p5.prototype); } diff --git a/src/color/setting.js b/src/color/setting.js index cf4310fed8..4c1a44a8da 100644 --- a/src/color/setting.js +++ b/src/color/setting.js @@ -5,9 +5,19 @@ */ import * as constants from '../core/constants'; -import { RGB, RGBP3, HSL, HSB, HWB, LAB, LCH, OKLAB, OKLCH } from './creating_reading'; +import { + RGB, + RGBP3, + HSL, + HSB, + HWB, + LAB, + LCH, + OKLAB, + OKLCH +} from './creating_reading'; -function setting(p5, fn){ +function setting(p5, fn) { /** * Starts defining a shape that will mask any shapes drawn afterward. * @@ -163,7 +173,7 @@ function setting(p5, fn){ * endShape(); * } */ - fn.beginClip = function(options = {}) { + fn.beginClip = function (options = {}) { this._renderer.beginClip(options); }; @@ -191,7 +201,7 @@ function setting(p5, fn){ * describe('A white triangle and circle on a gray background.'); * } */ - fn.endClip = function() { + fn.endClip = function () { this._renderer.endClip(); }; @@ -363,7 +373,7 @@ function setting(p5, fn){ * pop(); * } */ - fn.clip = function(callback, options) { + fn.clip = function (callback, options) { this._renderer.beginClip(options); callback(); this._renderer.endClip(options); @@ -577,7 +587,7 @@ function setting(p5, fn){ * @param {Number} [a] * @chainable */ - fn.background = function(...args) { + fn.background = function (...args) { return this._renderer.background(...args); }; @@ -642,7 +652,7 @@ function setting(p5, fn){ * @param {Number} [b] normalized blue value. * @param {Number} [a] normalized alpha value. */ - fn.clear = function(...args) { + fn.clear = function (...args) { const _r = args[0] || 0; const _g = args[1] || 0; const _b = args[2] || 0; @@ -1031,26 +1041,17 @@ function setting(p5, fn){ * @method colorMode * @return {RGB|HSB|HSL|RGBP3|HWB|LAB|LCH|OKLAB|OKLCH} The current color mode. */ - fn.colorMode = function(mode, max1, max2, max3, maxA) { + fn.colorMode = function (mode, max1, max2, max3, maxA) { // p5._validateParameters('colorMode', arguments); - if ( - [ - RGB, - RGBP3, - HSB, - HSL, - HWB, - LAB, - LCH, - OKLAB, - OKLCH - ].includes(mode) - ) { + if ([RGB, RGBP3, HSB, HSL, HWB, LAB, LCH, OKLAB, OKLCH].includes(mode)) { // Set color mode. this._renderer.states.setValue('colorMode', mode); // Set color maxes. - this._renderer.states.setValue('colorMaxes', this._renderer.states.colorMaxes.clone()); + this._renderer.states.setValue( + 'colorMaxes', + this._renderer.states.colorMaxes.clone() + ); const maxes = this._renderer.states.colorMaxes[mode]; if (arguments.length === 2) { maxes[0] = max1; // Red @@ -1287,7 +1288,7 @@ function setting(p5, fn){ * @method fill * @return {p5.Color} the current fill color. */ - fn.fill = function(...args) { + fn.fill = function (...args) { return this._renderer.fill(...args); }; @@ -1339,7 +1340,7 @@ function setting(p5, fn){ * box(45); * } */ - fn.noFill = function() { + fn.noFill = function () { this._renderer.noFill(); return this; }; @@ -1388,7 +1389,7 @@ function setting(p5, fn){ * box(45); * } */ - fn.noStroke = function() { + fn.noStroke = function () { this._renderer.states.setValue('strokeColor', null); return this; }; @@ -1609,7 +1610,7 @@ function setting(p5, fn){ * @method stroke * @return {p5.Color} the current stroke color. */ - fn.stroke = function(...args) { + fn.stroke = function (...args) { return this._renderer.stroke(...args); }; @@ -1686,7 +1687,7 @@ function setting(p5, fn){ * describe('A purple canvas with a pink square in the middle. A circle at the top-left partially erases its interior and a fully erases its outline.'); * } */ - fn.erase = function(opacityFill = 255, opacityStroke = 255) { + fn.erase = function (opacityFill = 255, opacityStroke = 255) { this._renderer.erase(opacityFill, opacityStroke); return this; @@ -1723,7 +1724,7 @@ function setting(p5, fn){ * describe('An orange canvas with two tall blue rectangles. A circular hole in the center erases the rectangle on the left but not the one on the right.'); * } */ - fn.noErase = function() { + fn.noErase = function () { this._renderer.noErase(); return this; }; @@ -2164,6 +2165,6 @@ function setting(p5, fn){ export default setting; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { setting(p5, p5.prototype); } diff --git a/src/core/README.md b/src/core/README.md index 49f5e3cadf..f9e009a4b3 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -1,13 +1,12 @@ # p5.js Core -This folder contains the core functionality of p5.js, including the init and main files, environment, constants, the friendly error system, and the graphics pipeline (with the exception of 3D/WebGL). [Here is a diagram]( -https://sketchboard.me/zBOkvPuQisyA#/) that outlines how the main graphics-related classes inherit from and extend one another. There is still more to be done to document this pipeline more clearly. +This folder contains the core functionality of p5.js, including the init and main files, environment, constants, the friendly error system, and the graphics pipeline (with the exception of 3D/WebGL). [Here is a diagram](https://sketchboard.me/zBOkvPuQisyA#/) that outlines how the main graphics-related classes inherit from and extend one another. There is still more to be done to document this pipeline more clearly. ![class diagram, interactive version viewable in above link](../../contributor_docs/images/p5-graphics-diagram.png) ## [init.js](./init.js) -The `init.js` module initializes a new `p5` object in either global mode, instance mode, or unit testing mode. Broadly speaking, this module should only contain logic that needs to execute *before startup*, such as testing the environment in which the code is running in order to make decisions about how it should load. +The `init.js` module initializes a new `p5` object in either global mode, instance mode, or unit testing mode. Broadly speaking, this module should only contain logic that needs to execute _before startup_, such as testing the environment in which the code is running in order to make decisions about how it should load. ## [main.js](./main.js) @@ -17,25 +16,27 @@ Aside from the exported constructor function, the most important thing in this m ## [structure.js](./structure.js) -The `structure.js` module is best thought of as a collection of ways to make *modifications* to the typical *loop structure* exposed by the `draw()` function. The functions in this module allow you to add and extract individual renders from the loop, draw the loop one frame at a time, pause and resume the loop, and so on. +The `structure.js` module is best thought of as a collection of ways to make _modifications_ to the typical _loop structure_ exposed by the `draw()` function. The functions in this module allow you to add and extract individual renders from the loop, draw the loop one frame at a time, pause and resume the loop, and so on. ## [constants.js](./constants.js) The `constants.js` module provides various default values mostly related to math and web standards using clear semantic names to increase syntax clarity when they are used in other algorithms. These values are attached to the `p5` object prototype as properties by the constructor function so they can be more easily accessed by other parts of the p5.js library or by user code. ## [friendly_errors/fes_core.js](./friendly_errors/fes_core.js) -This is the main file for the Friendly Error System (FES). The Friendly Error System (FES) is a system designed to help new programmers with common user errors as they get started learning. It catches common beginning errors and provides clear language and links to help a user resolve the error. FES is only applied to functions that are ones a user might encounter when they are just starting. You can learn more about the FES in the comment section at the top of this file, or in the [contributors_docs/friendly_error_system.md](https://github.com/processing/p5.js/blob/main/contributor_docs/friendly_error_system.md) file. + +This is the main file for the Friendly Error System (FES). The Friendly Error System (FES) is a system designed to help new programmers with common user errors as they get started learning. It catches common beginning errors and provides clear language and links to help a user resolve the error. FES is only applied to functions that are ones a user might encounter when they are just starting. You can learn more about the FES in the comment section at the top of this file, or in the [contributors_docs/friendly_error_system.md](https://github.com/processing/p5.js/blob/main/contributor_docs/friendly_error_system.md) file. ## [legacy.js](./legacy.js) + These are functions that are part of the Processing API but are not part of the p5.js API. In some cases they have a new name, in others, they are removed completely. Not all unsupported Processing functions are listed here but we try to include ones that a user coming from Processing might likely call. # Browser -*utilizing and abstracting web technologies* +_utilizing and abstracting web technologies_ ## [environment.js](./environment.js) -The `environment.js` module detects and semantically reassigns various features of the code execution environment that are arguably beyond the control of the p5.js library, such as device properties, window dimensions, and the URL bar. In general, this module should only contain code that exposes mechanics of the web that usually would not be considered part of the p5.js "sketch." This module is conceptually similar to `init.js` in that it tests the environment in which the p5.js code is executing, but one major difference is that broadly speaking the code in `environment.js` does *not* need to run solely at initialization as with `init.js`, and can be invoked whenever necessary. +The `environment.js` module detects and semantically reassigns various features of the code execution environment that are arguably beyond the control of the p5.js library, such as device properties, window dimensions, and the URL bar. In general, this module should only contain code that exposes mechanics of the web that usually would not be considered part of the p5.js "sketch." This module is conceptually similar to `init.js` in that it tests the environment in which the p5.js code is executing, but one major difference is that broadly speaking the code in `environment.js` does _not_ need to run solely at initialization as with `init.js`, and can be invoked whenever necessary. ## [shim.js](./shim.js) @@ -43,11 +44,11 @@ Sometimes p5.js needs to use new browser APIs or other features which haven't ye ## [p5.Element.js](./p5.Element.js) -Although p5.js is a general toolkit for all sorts of interactive art projects, in practice many p5.js sketches focus on rendering two dimensional graphics into a `` element. The `p5.Element.js` module is a wrapper around the browser's DOM API which is focused on the `` element and also handles user input events like clicks and mouse movements. Conceptually, p5.js considers the sketch to be *the entire web page*. It's useful to separate `` from the more general DOM because so many p5.js projects focus on `` and this enables smaller [custom builds](https://github.com/processing/p5.js/blob/main/contributor_docs/custom_p5_build.md). +Although p5.js is a general toolkit for all sorts of interactive art projects, in practice many p5.js sketches focus on rendering two dimensional graphics into a `` element. The `p5.Element.js` module is a wrapper around the browser's DOM API which is focused on the `` element and also handles user input events like clicks and mouse movements. Conceptually, p5.js considers the sketch to be _the entire web page_. It's useful to separate `` from the more general DOM because so many p5.js projects focus on `` and this enables smaller [custom builds](https://github.com/processing/p5.js/blob/main/contributor_docs/custom_p5_build.md). # Rendering -*project output options* +_project output options_ ## [rendering.js](./rendering.js) @@ -67,16 +68,15 @@ The `p5.Graphics` module is a lightweight wrapper around renderers which is used # Drawing -*lines, shapes, and visual styles* +_lines, shapes, and visual styles_ ## [transform.js](./transform.js) The `transform.js` module implements logic for scaling, rotation, and matrix transformations. - ## [shape/attributes.js](./shape/attributes.js) -Attributes are an important part of the general API for the web, but the `attributes.js` module defines and handles miscellaneous *graphical* attributes that can be consider a set of active parameters for the current drawing style. Think of this as a grab bag of all the remaining drawing parameters that were not moved out into separate dedicated modules as with [color](../color/) and [typography](../typography/). +Attributes are an important part of the general API for the web, but the `attributes.js` module defines and handles miscellaneous _graphical_ attributes that can be consider a set of active parameters for the current drawing style. Think of this as a grab bag of all the remaining drawing parameters that were not moved out into separate dedicated modules as with [color](../color/) and [typography](../typography/). ## [shape/2d_primitives.js](./shape/2d_primitives.js) diff --git a/src/core/environment.js b/src/core/environment.js index 0a9001caa3..da676c9066 100644 --- a/src/core/environment.js +++ b/src/core/environment.js @@ -7,9 +7,10 @@ import * as C from './constants'; // import { Vector } from '../math/p5.Vector'; -function environment(p5, fn, lifecycles){ +function environment(p5, fn, lifecycles) { const standardCursors = [C.ARROW, C.CROSS, C.HAND, C.MOVE, C.TEXT, C.WAIT]; - const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; + const isBrowser = + typeof window !== 'undefined' && typeof document !== 'undefined'; fn._frameRate = 0; fn._lastFrameTime = globalThis.performance.now(); @@ -18,13 +19,11 @@ function environment(p5, fn, lifecycles){ const windowPrint = isBrowser ? window.print : null; let windowPrintDisabled = false; - lifecycles.presetup = function(){ - const events = [ - 'resize' - ]; + lifecycles.presetup = function () { + const events = ['resize']; - if(isBrowser){ - for(const event of events){ + if (isBrowser) { + for (const event of events) { window.addEventListener(event, this[`_on${event}`].bind(this), { passive: false, signal: this._removeSignal @@ -59,7 +58,7 @@ function environment(p5, fn, lifecycles){ * print(`hello, ${name}`); * } */ - fn.print = function(...args) { + fn.print = function (...args) { if (!args.length && windowPrint !== null) { if (!windowPrintDisabled) { windowPrint(); @@ -288,7 +287,7 @@ function environment(p5, fn, lifecycles){ * @method cursor * @return {(ARROW|CROSS|HAND|MOVE|TEXT|WAIT|String)} the current cursor type */ - fn.cursor = function(type, x, y) { + fn.cursor = function (type, x, y) { let cursor = 'auto'; const canvas = this._curElement.elt; if (typeof type === 'undefined') { @@ -300,7 +299,8 @@ function environment(p5, fn, lifecycles){ cursor = type; } else if (typeof type === 'string') { let coords = ''; - if (typeof x === 'number') { // fix for #8323 + if (typeof x === 'number') { + // fix for #8323 y = typeof y === 'number' ? y : 0; // Note that x and y values must be unit-less positive integers < 32 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor @@ -394,7 +394,7 @@ function environment(p5, fn, lifecycles){ * @method frameRate * @return {Number} current frame rate. */ - fn.frameRate = function(fps) { + fn.frameRate = function (fps) { // p5._validateParameters('frameRate', arguments); if (typeof fps !== 'number' || fps < 0) { return this._frameRate; @@ -413,7 +413,7 @@ function environment(p5, fn, lifecycles){ * @private * @return {Number} current frameRate */ - fn.getFrameRate = function() { + fn.getFrameRate = function () { return this.frameRate(); }; @@ -429,7 +429,7 @@ function environment(p5, fn, lifecycles){ * @private * @param {Number} [fps] number of frames to be displayed every second */ - fn.setFrameRate = function(fps) { + fn.setFrameRate = function (fps) { return this.frameRate(fps); }; @@ -460,7 +460,7 @@ function environment(p5, fn, lifecycles){ * text(fps, 43, 54); * } */ - fn.getTargetFrameRate = function() { + fn.getTargetFrameRate = function () { return this._targetFrameRate; }; @@ -482,7 +482,7 @@ function environment(p5, fn, lifecycles){ * describe('A white circle on a gray background. The circle follows the mouse as it moves. The cursor is hidden.'); * } */ - fn.noCursor = function() { + fn.noCursor = function () { this._curElement.elt.style.cursor = 'none'; }; @@ -722,7 +722,7 @@ function environment(p5, fn, lifecycles){ * @alt * This example does not render anything. */ - fn._onresize = function(e) { + fn._onresize = function (e) { this.windowWidth = getWindowWidth(); this.windowHeight = getWindowHeight(); let executeDefault; @@ -746,7 +746,7 @@ function environment(p5, fn, lifecycles){ * Called upon each p5 instantiation instead of module import due to the * possibility of the window being resized when no sketch is active. */ - fn._updateWindowSize = function() { + fn._updateWindowSize = function () { this.windowWidth = getWindowWidth(); this.windowHeight = getWindowHeight(); }; @@ -754,7 +754,7 @@ function environment(p5, fn, lifecycles){ Object.defineProperty(fn, 'width', { configurable: true, enumerable: true, - get(){ + get() { return this._renderer?.width; } }); @@ -762,7 +762,7 @@ function environment(p5, fn, lifecycles){ Object.defineProperty(fn, 'height', { configurable: true, enumerable: true, - get(){ + get() { return this._renderer?.height; } }); @@ -798,7 +798,7 @@ function environment(p5, fn, lifecycles){ * } * } */ - fn.fullscreen = function(val) { + fn.fullscreen = function (val) { // no arguments, return fullscreen or not if (typeof val === 'undefined') { return ( @@ -868,7 +868,7 @@ function environment(p5, fn, lifecycles){ * @method pixelDensity * @returns {Number} current pixel density of the sketch. */ - fn.pixelDensity = function(val) { + fn.pixelDensity = function (val) { let returnValue; if (typeof val === 'number') { if (val !== this._renderer._pixelDensity) { @@ -997,8 +997,7 @@ function environment(p5, fn, lifecycles){ * describe('The word "reference" written in black on a gray background.'); * } */ - fn.getURLPath = () => - location.pathname.split('/').filter(v => v !== ''); + fn.getURLPath = () => location.pathname.split('/').filter(v => v !== ''); /** * Returns the current @@ -1033,7 +1032,7 @@ function environment(p5, fn, lifecycles){ * @alt * This example does not render anything. */ - fn.getURLParams = function() { + fn.getURLParams = function () { const re = /[?&]([^&=]+)(?:[&=])([^&=]+)/gim; let m; const v = {}; @@ -1158,7 +1157,7 @@ function environment(p5, fn, lifecycles){ * }); * } */ - fn.worldToScreen = function(worldPosition) { + fn.worldToScreen = function (worldPosition) { if (typeof worldPosition === 'number') { // We got passed numbers, convert to vector worldPosition = this.createVector(...arguments); @@ -1205,7 +1204,7 @@ function environment(p5, fn, lifecycles){ * line(localMouse.x, -30, localMouse.x, 30); * } */ - fn.screenToWorld = function(screenPosition) { + fn.screenToWorld = function (screenPosition) { if (typeof screenPosition === 'number') { // We got passed numbers, convert to vector screenPosition = this.createVector(...arguments); @@ -1222,8 +1221,8 @@ function environment(p5, fn, lifecycles){ const matrixInverse = matrix.invert(matrix); - const worldPosition = matrixInverse - .multiplyAndNormalizePoint(screenPosition); + const worldPosition = + matrixInverse.multiplyAndNormalizePoint(screenPosition); return worldPosition; }; @@ -1346,6 +1345,6 @@ function environment(p5, fn, lifecycles){ export default environment; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { environment(p5, p5.prototype); } diff --git a/src/core/filterShaders.js b/src/core/filterShaders.js index 103b6bd81d..9c265c3c33 100644 --- a/src/core/filterShaders.js +++ b/src/core/filterShaders.js @@ -12,186 +12,224 @@ import * as constants from './constants'; export function makeFilterShader(renderer, operation, p5) { switch (operation) { case constants.GRAY: - return renderer.baseFilterShader().modify(({ p5 }) => { - p5.getColor((inputs, canvasContent) => { - const tex = p5.getTexture(canvasContent, inputs.texCoord); - // weighted grayscale with luminance values - const gray = p5.dot(tex.rgb, p5.vec3(0.2126, 0.7152, 0.0722)); - return p5.vec4(gray, gray, gray, tex.a); - }); - }, { p5 }); + return renderer.baseFilterShader().modify( + ({ p5 }) => { + p5.getColor((inputs, canvasContent) => { + const tex = p5.getTexture(canvasContent, inputs.texCoord); + // weighted grayscale with luminance values + const gray = p5.dot(tex.rgb, p5.vec3(0.2126, 0.7152, 0.0722)); + return p5.vec4(gray, gray, gray, tex.a); + }); + }, + { p5 } + ); case constants.INVERT: - return renderer.baseFilterShader().modify(({ p5 }) => { - p5.getColor((inputs, canvasContent) => { - const color = p5.getTexture(canvasContent, inputs.texCoord); - const invertedColor = p5.vec3(1.0) - color.rgb; - return p5.vec4(invertedColor, color.a); - }); - }, { p5 }); + return renderer.baseFilterShader().modify( + ({ p5 }) => { + p5.getColor((inputs, canvasContent) => { + const color = p5.getTexture(canvasContent, inputs.texCoord); + const invertedColor = p5.vec3(1.0) - color.rgb; + return p5.vec4(invertedColor, color.a); + }); + }, + { p5 } + ); case constants.THRESHOLD: - return renderer.baseFilterShader().modify(({ p5 }) => { - const filterParameter = p5.uniformFloat('filterParameter'); - p5.getColor((inputs, canvasContent) => { - const color = p5.getTexture(canvasContent, inputs.texCoord); - // weighted grayscale with luminance values - const gray = p5.dot(color.rgb, p5.vec3(0.2126, 0.7152, 0.0722)); - const threshold = p5.floor(filterParameter * 255.0) / 255; - const blackOrWhite = p5.step(threshold, gray); - return p5.vec4(p5.vec3(blackOrWhite), color.a); - }); - }, { p5 }); + return renderer.baseFilterShader().modify( + ({ p5 }) => { + const filterParameter = p5.uniformFloat('filterParameter'); + p5.getColor((inputs, canvasContent) => { + const color = p5.getTexture(canvasContent, inputs.texCoord); + // weighted grayscale with luminance values + const gray = p5.dot(color.rgb, p5.vec3(0.2126, 0.7152, 0.0722)); + const threshold = p5.floor(filterParameter * 255.0) / 255; + const blackOrWhite = p5.step(threshold, gray); + return p5.vec4(p5.vec3(blackOrWhite), color.a); + }); + }, + { p5 } + ); case constants.POSTERIZE: - return renderer.baseFilterShader().modify(({ p5 }) => { - const filterParameter = p5.uniformFloat('filterParameter'); - const quantize = (color, n) => { - // restrict values to N options/bins - // and floor each channel to nearest value - // - // eg. when N = 5, values = 0.0, 0.25, 0.50, 0.75, 1.0 - // then quantize (0.1, 0.7, 0.9) -> (0.0, 0.5, 1.0) - - color = color * n; - color = p5.floor(color); - color = color / (n - 1.0); - return color; - }; - p5.getColor((inputs, canvasContent) => { - const color = p5.getTexture(canvasContent, inputs.texCoord); - const restrictedColor = quantize(color.rgb, filterParameter); - return p5.vec4(restrictedColor, color.a); - }); - }, { p5 }); + return renderer.baseFilterShader().modify( + ({ p5 }) => { + const filterParameter = p5.uniformFloat('filterParameter'); + const quantize = (color, n) => { + // restrict values to N options/bins + // and floor each channel to nearest value + // + // eg. when N = 5, values = 0.0, 0.25, 0.50, 0.75, 1.0 + // then quantize (0.1, 0.7, 0.9) -> (0.0, 0.5, 1.0) + + color = color * n; + color = p5.floor(color); + color = color / (n - 1.0); + return color; + }; + p5.getColor((inputs, canvasContent) => { + const color = p5.getTexture(canvasContent, inputs.texCoord); + const restrictedColor = quantize(color.rgb, filterParameter); + return p5.vec4(restrictedColor, color.a); + }); + }, + { p5 } + ); case constants.BLUR: - return renderer.baseFilterShader().modify(({ p5 }) => { - const radius = p5.uniformFloat('radius'); - const direction = p5.uniformVec2('direction'); - - // This isn't a real Gaussian weight, it's a quadratic weight - const quadWeight = (x, e) => { - return p5.pow(e - p5.abs(x), 2.0); - }; - - const random = (p) => { - let p3 = p5.fract(p.xyx * .1031); - p3 += p5.dot(p3, p3.yzx + 33.33); - return p5.fract((p3.x + p3.y) * p3.z); - }; - - p5.getColor((inputs, canvasContent) => { - const uv = inputs.texCoord; - - // A reasonable maximum number of samples - const maxSamples = 64.0; - - let numSamples = p5.floor(radius * 7.0); - if (p5.mod(numSamples, 2) == 0.0) { - numSamples++; - } - - let avg = p5.vec4(0.0); - let total = 0.0; - - // Calculate the spacing to avoid skewing if numSamples > maxSamples - let spacing = 1.0; - if (numSamples > maxSamples) { - spacing = numSamples / maxSamples; - numSamples = maxSamples; - } - - const randomOffset = (spacing - 1.0) * p5.mix(-0.5, 0.5, random(uv * inputs.canvasSize)); - for (let i = 0; i < numSamples; i++) { - const sample = i * spacing - (numSamples - 1.0) * 0.5 * spacing + randomOffset; - const sampleCoord = uv + p5.vec2(sample, sample) / inputs.canvasSize * direction; - const weight = quadWeight(sample, (numSamples - 1.0) * 0.5 * spacing); - - const texSample = p5.getTexture(canvasContent, sampleCoord); - avg += weight * texSample * p5.vec4( - texSample.a, texSample.a, texSample.a, 1 + return renderer.baseFilterShader().modify( + ({ p5 }) => { + const radius = p5.uniformFloat('radius'); + const direction = p5.uniformVec2('direction'); + + // This isn't a real Gaussian weight, it's a quadratic weight + const quadWeight = (x, e) => { + return p5.pow(e - p5.abs(x), 2.0); + }; + + const random = p => { + let p3 = p5.fract(p.xyx * 0.1031); + p3 += p5.dot(p3, p3.yzx + 33.33); + return p5.fract((p3.x + p3.y) * p3.z); + }; + + p5.getColor((inputs, canvasContent) => { + const uv = inputs.texCoord; + + // A reasonable maximum number of samples + const maxSamples = 64.0; + + let numSamples = p5.floor(radius * 7.0); + if (p5.mod(numSamples, 2) == 0.0) { + numSamples++; + } + + let avg = p5.vec4(0.0); + let total = 0.0; + + // Calculate the spacing to avoid skewing if numSamples > maxSamples + let spacing = 1.0; + if (numSamples > maxSamples) { + spacing = numSamples / maxSamples; + numSamples = maxSamples; + } + + const randomOffset = + (spacing - 1.0) * + p5.mix(-0.5, 0.5, random(uv * inputs.canvasSize)); + for (let i = 0; i < numSamples; i++) { + const sample = + i * spacing - (numSamples - 1.0) * 0.5 * spacing + randomOffset; + const sampleCoord = + uv + (p5.vec2(sample, sample) / inputs.canvasSize) * direction; + const weight = quadWeight( + sample, + (numSamples - 1.0) * 0.5 * spacing + ); + + const texSample = p5.getTexture(canvasContent, sampleCoord); + avg += + weight * + texSample * + p5.vec4(texSample.a, texSample.a, texSample.a, 1); + total += weight; + } + + const blended = avg / total; + return p5.vec4( + blended.r / blended.a, + blended.g / blended.a, + blended.b / blended.a, + blended.a ); - total += weight; - } - - const blended = avg / total; - return p5.vec4( - blended.r / blended.a, - blended.g / blended.a, - blended.b / blended.a, - blended.a - ); - }); - }, { p5 }); + }); + }, + { p5 } + ); case constants.ERODE: - return renderer.baseFilterShader().modify(({ p5 }) => { - const luma = (color) => { - return p5.dot(color.rgb, p5.vec3(0.2126, 0.7152, 0.0722)); - }; - - p5.getColor((inputs, canvasContent) => { - const uv = inputs.texCoord; - let minColor = p5.getTexture(canvasContent, uv); - let minLuma = luma(minColor); - - for (let x = -1; x <= 1; x++) { - for (let y = -1; y <= 1; y++) { - if (x != 0 || y != 0) { - const offset = p5.vec2(x, y) * inputs.texelSize; - const neighborColor = p5.getTexture(canvasContent, uv + offset); - const neighborLuma = luma(neighborColor); - - if (neighborLuma < minLuma) { - minLuma = neighborLuma; - minColor = neighborColor; + return renderer.baseFilterShader().modify( + ({ p5 }) => { + const luma = color => { + return p5.dot(color.rgb, p5.vec3(0.2126, 0.7152, 0.0722)); + }; + + p5.getColor((inputs, canvasContent) => { + const uv = inputs.texCoord; + let minColor = p5.getTexture(canvasContent, uv); + let minLuma = luma(minColor); + + for (let x = -1; x <= 1; x++) { + for (let y = -1; y <= 1; y++) { + if (x != 0 || y != 0) { + const offset = p5.vec2(x, y) * inputs.texelSize; + const neighborColor = p5.getTexture( + canvasContent, + uv + offset + ); + const neighborLuma = luma(neighborColor); + + if (neighborLuma < minLuma) { + minLuma = neighborLuma; + minColor = neighborColor; + } } } } - } - return minColor; - }); - }, { p5 }); + return minColor; + }); + }, + { p5 } + ); case constants.DILATE: - return renderer.baseFilterShader().modify(({ p5 }) => { - const luma = (color) => { - return p5.dot(color.rgb, p5.vec3(0.2126, 0.7152, 0.0722)); - }; - - p5.getColor((inputs, canvasContent) => { - const uv = inputs.texCoord; - let maxColor = p5.getTexture(canvasContent, uv); - let maxLuma = luma(maxColor); - - for (let x = -1; x <= 1; x++) { - for (let y = -1; y <= 1; y++) { - if (x != 0 || y != 0) { - const offset = p5.vec2(x, y) * inputs.texelSize; - const neighborColor = p5.getTexture(canvasContent, uv + offset); - const neighborLuma = luma(neighborColor); - - if (neighborLuma > maxLuma) { - maxLuma = neighborLuma; - maxColor = neighborColor; + return renderer.baseFilterShader().modify( + ({ p5 }) => { + const luma = color => { + return p5.dot(color.rgb, p5.vec3(0.2126, 0.7152, 0.0722)); + }; + + p5.getColor((inputs, canvasContent) => { + const uv = inputs.texCoord; + let maxColor = p5.getTexture(canvasContent, uv); + let maxLuma = luma(maxColor); + + for (let x = -1; x <= 1; x++) { + for (let y = -1; y <= 1; y++) { + if (x != 0 || y != 0) { + const offset = p5.vec2(x, y) * inputs.texelSize; + const neighborColor = p5.getTexture( + canvasContent, + uv + offset + ); + const neighborLuma = luma(neighborColor); + + if (neighborLuma > maxLuma) { + maxLuma = neighborLuma; + maxColor = neighborColor; + } } } } - } - return maxColor; - }); - }, { p5 }); + return maxColor; + }); + }, + { p5 } + ); case constants.OPAQUE: - return renderer.baseFilterShader().modify(({ p5 }) => { - p5.getColor((inputs, canvasContent) => { - const color = p5.getTexture(canvasContent, inputs.texCoord); - return p5.vec4(color.rgb, 1.0); - }); - }, { p5 }); + return renderer.baseFilterShader().modify( + ({ p5 }) => { + p5.getColor((inputs, canvasContent) => { + const color = p5.getTexture(canvasContent, inputs.texCoord); + return p5.vec4(color.rgb, 1.0); + }); + }, + { p5 } + ); default: throw new Error(`Unknown filter: ${operation}`); diff --git a/src/core/helpers.js b/src/core/helpers.js index 277fbf2f5f..16f470b029 100644 --- a/src/core/helpers.js +++ b/src/core/helpers.js @@ -13,7 +13,6 @@ function modeAdjust(a, b, c, d, mode) { let bbox; if (mode === constants.CORNER) { - // CORNER mode already corresponds to a bounding box (top-left corner, width, height). // For negative widths or heights, the absolute value is used. bbox = { @@ -22,9 +21,7 @@ function modeAdjust(a, b, c, d, mode) { w: Math.abs(c), h: Math.abs(d) }; - } else if (mode === constants.CORNERS) { - // CORNERS mode uses two opposite corners, in any configuration. // Make sure to get the top left corner by using the minimum of the x and y coordinates. bbox = { @@ -33,9 +30,7 @@ function modeAdjust(a, b, c, d, mode) { w: Math.abs(c - a), h: Math.abs(d - b) }; - } else if (mode === constants.RADIUS) { - // RADIUS mode uses the center point and half the width and height. // c (half width) and d (half height) could be negative, so use the absolute value // in calculating the top left corner (x, y). @@ -47,21 +42,18 @@ function modeAdjust(a, b, c, d, mode) { w: 2 * c, h: 2 * d }; - } else if (mode === constants.CENTER) { - // CENTER mode uses the center point, width and height. // c (width) and d (height) could be negative, so use the absolute value // in calculating the top-left corner (x, y). c = Math.abs(c); d = Math.abs(d); bbox = { - x: a - (c * 0.5), - y: b - (d * 0.5), + x: a - c * 0.5, + y: b - d * 0.5, w: c, h: d }; - } return bbox; diff --git a/src/core/init.js b/src/core/init.js index 736fd3cec1..b4396212d8 100644 --- a/src/core/init.js +++ b/src/core/init.js @@ -12,7 +12,7 @@ import p5 from '../core/main'; * @return {Undefined} */ export const _globalInit = () => { - if(typeof window === 'undefined') return; + if (typeof window === 'undefined') return; // Could have been any property defined within the p5 constructor. // If that property is already a part of the global object, // this code has already run before, likely due to a duplicate import @@ -40,8 +40,8 @@ export const _globalInit = () => { }; // make a promise that resolves when the document is ready -export const waitForDocumentReady = () =>{ - if(typeof document !== 'undefined'){ +export const waitForDocumentReady = () => { + if (typeof document !== 'undefined') { return new Promise((resolve, reject) => { // if the page is ready, initialize p5 immediately if (document.readyState === 'complete') { diff --git a/src/core/legacy.js b/src/core/legacy.js index 6253b8894e..9db12139f9 100644 --- a/src/core/legacy.js +++ b/src/core/legacy.js @@ -9,19 +9,19 @@ import p5 from './main'; -p5.prototype.pushStyle = function() { +p5.prototype.pushStyle = function () { throw new Error('pushStyle() not used, see push()'); }; -p5.prototype.popStyle = function() { +p5.prototype.popStyle = function () { throw new Error('popStyle() not used, see pop()'); }; -p5.prototype.popMatrix = function() { +p5.prototype.popMatrix = function () { throw new Error('popMatrix() not used, see pop()'); }; -p5.prototype.pushMatrix = function() { +p5.prototype.pushMatrix = function () { throw new Error('pushMatrix() not used, see push()'); }; diff --git a/src/core/main.js b/src/core/main.js index 00e536d900..5ce74b9b54 100644 --- a/src/core/main.js +++ b/src/core/main.js @@ -49,7 +49,7 @@ class p5 { constructor(sketch, node) { // Apply addon defined decorations - if(p5.decorations.size > 0){ + if (p5.decorations.size > 0) { decorateClass(p5, p5.decorations, 'p5'); p5.decorations.clear(); } @@ -70,8 +70,7 @@ class p5 { this._loop = true; this._startListener = null; this._initializeInstanceVariables(); - this._events = { - }; + this._events = {}; this._removeAbortController = new AbortController(); this._removeSignal = this._removeAbortController.signal; this._millisStart = -1; @@ -97,7 +96,7 @@ class p5 { // Loop through methods on the prototype and attach them to the window // All methods and properties with name starting with '_' will be skipped for (const p of Object.getOwnPropertyNames(p5.prototype)) { - if(p[0] === '_') continue; + if (p[0] === '_') continue; bindGlobal(p); } @@ -105,7 +104,7 @@ class p5 { // Attach its properties to the window for (const p in this) { if (this.hasOwnProperty(p)) { - if(p[0] === '_' || protectedProperties.includes(p)) continue; + if (p[0] === '_' || protectedProperties.includes(p)) continue; bindGlobal(p); } } @@ -126,10 +125,10 @@ class p5 { this.focused = false; }; - if(typeof window !== 'undefined'){ + if (typeof window !== 'undefined') { window.addEventListener('focus', focusHandler); window.addEventListener('blur', blurHandler); - p5.lifecycleHooks.remove.push(function() { + p5.lifecycleHooks.remove.push(function () { window.removeEventListener('focus', focusHandler); window.removeEventListener('blur', blurHandler); }); @@ -141,16 +140,16 @@ class p5 { this._startListener = this.#_start.bind(this); window.addEventListener('load', this._startListener, false); } - }else{ + } else { this.#_start(); } } - get pixels(){ + get pixels() { return this._renderer?.pixels; } - get drawingContext(){ + get drawingContext() { return this._renderer?.drawingContext; } @@ -167,43 +166,48 @@ class p5 { addon(p5, p5.prototype, lifecycles); const validLifecycles = Object.keys(p5.lifecycleHooks); - for(const name of validLifecycles){ - if(typeof lifecycles[name] === 'function'){ + for (const name of validLifecycles) { + if (typeof lifecycles[name] === 'function') { p5.lifecycleHooks[name].push(lifecycles[name]); } } } static decorations = new Map(); - static registerDecorator(pattern, decoration){ - if(typeof pattern === 'string'){ + static registerDecorator(pattern, decoration) { + if (typeof pattern === 'string') { const patternStr = pattern; pattern = ({ path }) => patternStr === path; - }else if( + } else if ( Array.isArray(pattern) && pattern.every(value => typeof value === 'string') - ){ + ) { const patternArray = pattern; pattern = ({ path }) => patternArray.includes(path); - }else if(typeof pattern !== 'function'){ - throw new Error('Decorator matching pattern must be a function, a string, or an array of strings'); + } else if (typeof pattern !== 'function') { + throw new Error( + 'Decorator matching pattern must be a function, a string, or an array of strings' + ); } p5.decorations.set(pattern, decoration); } #customActions = {}; - _customActions = new Proxy({}, { - get: (target, prop) => { - if(!this.#customActions[prop]){ - const context = this._isGlobal ? window : this; - if(typeof context[prop] === 'function'){ - this.#customActions[prop] = context[prop].bind(this); + _customActions = new Proxy( + {}, + { + get: (target, prop) => { + if (!this.#customActions[prop]) { + const context = this._isGlobal ? window : this; + if (typeof context[prop] === 'function') { + this.#customActions[prop] = context[prop].bind(this); + } } - } - return this.#customActions[prop]; + return this.#customActions[prop]; + } } - }); + ); async #_start() { if (this.hitCriticalError) return; @@ -229,12 +233,8 @@ class p5 { // Always create a default canvas. // Later on if the user calls createCanvas, this default one // will be replaced - if(typeof window !== 'undefined'){ - this.createCanvas( - 100, - 100, - constants.P2D - ); + if (typeof window !== 'undefined') { + this.createCanvas(100, 100, constants.P2D); } // Record the time when setup starts. millis() will start at 0 within @@ -247,7 +247,7 @@ class p5 { } if (this.hitCriticalError) return; - if(typeof document !== 'undefined'){ + if (typeof document !== 'undefined') { const canvases = document.getElementsByTagName('canvas'); for (const k of canvases) { // Apply touchAction = 'none' to canvases to prevent scrolling @@ -305,8 +305,10 @@ class p5 { this.deltaTime = now - this._lastRealFrameTime; this._frameRate = 1000.0 / this.deltaTime; await this.redraw(); - this._lastTargetFrameTime = Math.max(this._lastTargetFrameTime - + targetTimeBetweenFrames, now); + this._lastTargetFrameTime = Math.max( + this._lastTargetFrameTime + targetTimeBetweenFrames, + now + ); this._lastRealFrameTime = now; // If the user is actually using mouse module, then update @@ -327,9 +329,10 @@ class p5 { // an opportunity to draw. if (this._loop) { const boundDraw = this._draw.bind(this); - this._requestAnimId = typeof window !== 'undefined' ? - window.requestAnimationFrame(boundDraw) : - setImmediate(boundDraw); + this._requestAnimId = + typeof window !== 'undefined' + ? window.requestAnimationFrame(boundDraw) + : setImmediate(boundDraw); } } @@ -367,7 +370,7 @@ class p5 { */ async remove() { // Remove start listener to prevent orphan canvas being created - if(this._startListener){ + if (this._startListener) { window.removeEventListener('load', this._startListener, false); } @@ -415,9 +418,11 @@ class p5 { } async _runLifecycleHook(hookName) { - await Promise.all(p5.lifecycleHooks[hookName].map(hook => { - return hook.call(this); - })); + await Promise.all( + p5.lifecycleHooks[hookName].map(hook => { + return hook.call(this); + }) + ); } _initializeInstanceVariables() { @@ -453,8 +458,9 @@ function createBindGlobal(instance) { p5.prototype, property ); - const hasGetter = (instanceDescriptor && instanceDescriptor.get) || - (prototypeDescriptor && prototypeDescriptor.get); + const hasGetter = + (instanceDescriptor && instanceDescriptor.get) || + (prototypeDescriptor && prototypeDescriptor.get); // Only check if it's a function if it doesn't have a getter // to avoid actually evaluating getters before things like the @@ -530,70 +536,70 @@ function createBindGlobal(instance) { } // Generic function to decorate classes -function decorateClass(Target, decorations, path){ +function decorateClass(Target, decorations, path) { // Static properties - for(const key in Target){ - if(!key.startsWith('_')){ + for (const key in Target) { + if (!key.startsWith('_')) { for (const [pattern, decorator] of decorations) { - if(pattern({ path: `${path}.${key}` })){ + if (pattern({ path: `${path}.${key}` })) { // Check if method or accessor - if(typeof Target[key] === 'function'){ + if (typeof Target[key] === 'function') { const result = decorator(Target[key], { kind: 'method', name: key, static: true }); - if(result){ + if (result) { Object.defineProperty(Target, key, { enumerable: true, writable: true, value: result }); } - }else{ + } else { const result = decorator(undefined, { kind: 'field', name: key, static: true }); - if(result && typeof result === 'function'){ + if (result && typeof result === 'function') { Target[key] = result(Target[key]); } } } } - if(typeof Target[key] === 'function' && Target[key].prototype){ + if (typeof Target[key] === 'function' && Target[key].prototype) { decorateClass(Target[key], decorations, `${path}.${key}`); } } } // Member properties - for(const member of Object.getOwnPropertyNames(Target.prototype)){ - if(member !== 'constructor' && !member.startsWith('_')){ + for (const member of Object.getOwnPropertyNames(Target.prototype)) { + if (member !== 'constructor' && !member.startsWith('_')) { for (const [pattern, decorator] of decorations) { - if(pattern({ path: `${path}.prototype.${member}` })){ + if (pattern({ path: `${path}.prototype.${member}` })) { // Check if method or accessor - if(typeof Target.prototype[member] === 'function'){ + if (typeof Target.prototype[member] === 'function') { const result = decorator(Target.prototype[member], { kind: 'method', name: member, static: false }); - if(result) { + if (result) { Object.defineProperty(Target.prototype, member, { enumerable: true, writable: true, value: result }); } - }else{ + } else { const descriptor = Object.getOwnPropertyDescriptor( Target.prototype, member ); - if(descriptor.hasOwnProperty('value')){ + if (descriptor.hasOwnProperty('value')) { const result = decorator(undefined, { kind: 'field', name: member, @@ -602,11 +608,12 @@ function decorateClass(Target, decorations, path){ Object.defineProperty(Target.prototype, member, { enumerable: true, writable: true, - value: result && typeof result === 'function' ? - result(Target.prototype[member]) : - Target.prototype[member] + value: + result && typeof result === 'function' + ? result(Target.prototype[member]) + : Target.prototype[member] }); - }else{ + } else { const { get, set } = descriptor; const getterResult = decorator(get, { kind: 'getter', diff --git a/src/core/noop.js b/src/core/noop.js index 1f7eae86f1..713fd35363 100644 --- a/src/core/noop.js +++ b/src/core/noop.js @@ -1,4 +1,4 @@ export default function (p5) { - p5._friendlyError = () => {}; - p5._checkForUserDefinedFunctions = () => {}; - } + p5._friendlyError = () => {}; + p5._checkForUserDefinedFunctions = () => {}; +} diff --git a/src/core/p5.Graphics.js b/src/core/p5.Graphics.js index 26f5b2d87b..460f0369ee 100644 --- a/src/core/p5.Graphics.js +++ b/src/core/p5.Graphics.js @@ -42,31 +42,31 @@ class Graphics { return this.canvas; } - get deltaTime(){ + get deltaTime() { return this._pInst.deltaTime; } - get canvas(){ + get canvas() { return this._renderer?.canvas; } - get drawingContext(){ + get drawingContext() { return this._renderer.drawingContext; } - get width(){ + get width() { return this._renderer?.width; } - get height(){ + get height() { return this._renderer?.height; } - get pixels(){ + get pixels() { return this._renderer?.pixels; } - pixelDensity(val){ + pixelDensity(val) { let returnValue; if (typeof val === 'number') { if (val !== this._renderer._pixelDensity) { @@ -80,7 +80,7 @@ class Graphics { return returnValue; } - resizeCanvas(w, h){ + resizeCanvas(w, h) { this._renderer.resize(w, h); } @@ -550,7 +550,7 @@ class Graphics { throw new Error( `${name}() is only supported in WEBGL mode. If you'd like to use 3D graphics and WebGL, see https://p5js.org/examples/form-3d-primitives.html for more information.` ); - }; + } _initializeInstanceVariables() { this._accessibleOutputs = { @@ -571,9 +571,9 @@ class Graphics { this._downKeys = {}; //Holds the key codes of currently pressed keys } -}; +} -function graphics(p5, fn){ +function graphics(p5, fn) { /** * A class to describe a drawing surface that's separate from the main canvas. * diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index ae5082644a..7cb02da22e 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -31,7 +31,7 @@ class ClonableObject { clone() { return new ClonableObject(this); } -}; +} class Renderer { static states = { @@ -71,9 +71,8 @@ class Renderer { this._isMainCanvas = isMainCanvas; this.pixels = []; - const defaultRatio = typeof window !== 'undefined' ? - Math.ceil(window.devicePixelRatio) : - 1; + const defaultRatio = + typeof window !== 'undefined' ? Math.ceil(window.devicePixelRatio) : 1; if (isMainCanvas) { this._pixelDensity = defaultRatio; } else { @@ -113,11 +112,9 @@ class Renderer { return this._currentShape; } - remove() { - - } + remove() {} - pixelDensity(val){ + pixelDensity(val) { let returnValue; if (typeof val === 'number') { if (val !== this._pixelDensity) { @@ -171,7 +168,10 @@ class Renderer { if (value === undefined) { return this.states.splineProperties[key]; } else { - this.states.setValue('splineProperties', this.states.splineProperties.clone()); + this.states.setValue( + 'splineProperties', + this.states.splineProperties.clone() + ); this.states.splineProperties[key] = value; } this.updateShapeProperties(); @@ -271,7 +271,9 @@ class Renderer { beginClip(options = {}) { if (this._clipping) { - throw new Error("It looks like you're trying to clip while already in the middle of clipping. Did you forget to endClip()?"); + throw new Error( + "It looks like you're trying to clip while already in the middle of clipping. Did you forget to endClip()?" + ); } this._clipping = true; this._clipInvert = options.invert; @@ -279,7 +281,9 @@ class Renderer { endClip() { if (!this._clipping) { - throw new Error("It looks like you've called endClip() without beginClip(). Did you forget to call beginClip() first?"); + throw new Error( + "It looks like you've called endClip() without beginClip(). Did you forget to call beginClip() first?" + ); } this._clipping = false; } @@ -297,7 +301,7 @@ class Renderer { const canvas = this.canvas; if (typeof x === 'undefined' && typeof y === 'undefined') { - // get() + // get() x = y = 0; w = this.width; h = this.height; @@ -306,28 +310,26 @@ class Renderer { y *= pd; if (typeof w === 'undefined' && typeof h === 'undefined') { - // get(x,y) + // get(x,y) if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) { return [0, 0, 0, 0]; } return this._getPixel(x, y); } - // get(x,y,w,h) + // get(x,y,w,h) } - const region = new Image(w*pd, h*pd); + const region = new Image(w * pd, h * pd); region.pixelDensity(pd); region.canvas .getContext('2d') - .drawImage(canvas, x, y, w * pd, h * pd, 0, 0, w*pd, h*pd); + .drawImage(canvas, x, y, w * pd, h * pd, 0, 0, w * pd, h * pd); return region; } - scale(x, y){ - - } + scale(x, y) {} fill(...args) { if (args.length > 0) { @@ -404,7 +406,7 @@ class Renderer { //// TEXT SUPPORT METHODS ////////////////////////////// - _middleAlignOffset = function() { + _middleAlignOffset = function () { const { textFont, textSize } = this.states; const font = textFont?.font; const ctx = this.textDrawingContext(); @@ -418,9 +420,9 @@ class Renderer { } return metrics.alphabeticBaseline + sCapHeight / 2; }; -}; +} -function renderer(p5, fn){ +function renderer(p5, fn) { /** * Main graphics and rendering context, as well as the base API * implementation for p5.js "core". To be used as the superclass for diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index 06b199399c..5a710390c5 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -28,7 +28,7 @@ class Renderer2D extends Renderer { this.canvas.style.display = 'none'; } - if(!this.elt.id){ + if (!this.elt.id) { this.elt.id = `defaultCanvas${p5.sketchCount++}`; } this.elt.classList.add('p5Canvas'); @@ -66,7 +66,7 @@ class Renderer2D extends Renderer { // Get and store drawing context this.drawingContext = this.canvas.getContext('2d', attributes); - if(attributes.colorSpace === 'display-p3'){ + if (attributes.colorSpace === 'display-p3') { this.states.colorMode = RGBP3; } this.scale(this._pixelDensity, this._pixelDensity); @@ -83,7 +83,7 @@ class Renderer2D extends Renderer { return this._filterRenderer; } - remove(){ + remove() { this.wrappedElt.remove(); this.wrappedElt = null; this.canvas = null; @@ -96,13 +96,12 @@ class Renderer2D extends Renderer { const pInst = this._pInst; // create secondary layer - this.filterGraphicsLayer = - new Graphics( - this.width, - this.height, - constants.WEBGL, - pInst - ); + this.filterGraphicsLayer = new Graphics( + this.width, + this.height, + constants.WEBGL, + pInst + ); } if ( this.filterGraphicsLayer.width !== this.width || @@ -146,10 +145,7 @@ class Renderer2D extends Renderer { this.canvas.height = h * this._pixelDensity; this.canvas.style.width = `${w}px`; this.canvas.style.height = `${h}px`; - this.drawingContext.scale( - this._pixelDensity, - this._pixelDensity - ); + this.drawingContext.scale(this._pixelDensity, this._pixelDensity); // reset canvas properties for (const savedKey in props) { @@ -167,7 +163,7 @@ class Renderer2D extends Renderer { background(...args) { if (args.length === 0) { - return this;// setter with no args does nothing + return this; // setter with no args does nothing } this.push(); this.resetMatrix(); @@ -224,7 +220,10 @@ class Renderer2D extends Renderer { // Add accessible outputs if the method exists; on success, // set the accessible output background to white. if (this._pInst._addAccsOutput?.()) { - this._pInst._accsCanvasColors?.('fill', color._getRGBA([255, 255, 255, 255])); + this._pInst._accsCanvasColors?.( + 'fill', + color._getRGBA([255, 255, 255, 255]) + ); } } @@ -239,7 +238,10 @@ class Renderer2D extends Renderer { // Add accessible outputs if the method exists; on success, // set the accessible output background to white. if (this._pInst._addAccsOutput?.()) { - this._pInst._accsCanvasColors?.('stroke', color._getRGBA([255, 255, 255, 255])); + this._pInst._accsCanvasColors?.( + 'stroke', + color._getRGBA([255, 255, 255, 255]) + ); } } @@ -251,7 +253,10 @@ class Renderer2D extends Renderer { this.drawingContext.fillStyle = newFill; // cache the stroke style - this.states.setValue('_cachedStrokeStyle', this.drawingContext.strokeStyle); + this.states.setValue( + '_cachedStrokeStyle', + this.drawingContext.strokeStyle + ); const newStroke = this._pInst.color(255, opacityStroke).toString(); this.drawingContext.strokeStyle = newStroke; @@ -322,22 +327,10 @@ class Renderer2D extends Renderer { if (this._clipInvert) { // Slight hack: draw a big rectangle over everything with reverse winding // order. This is hopefully large enough to cover most things. - this.clipPath.moveTo( - -2 * this.width, - -2 * this.height - ); - this.clipPath.lineTo( - -2 * this.width, - 2 * this.height - ); - this.clipPath.lineTo( - 2 * this.width, - 2 * this.height - ); - this.clipPath.lineTo( - 2 * this.width, - -2 * this.height - ); + this.clipPath.moveTo(-2 * this.width, -2 * this.height); + this.clipPath.lineTo(-2 * this.width, 2 * this.height); + this.clipPath.lineTo(2 * this.width, 2 * this.height); + this.clipPath.lineTo(2 * this.width, -2 * this.height); this.clipPath.closePath(); } } @@ -362,17 +355,7 @@ class Renderer2D extends Renderer { // IMAGE | Loading & Displaying ////////////////////////////////////////////// - image( - img, - sx, - sy, - sWidth, - sHeight, - dx, - dy, - dWidth, - dHeight - ) { + image(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) { let cnv; if (img.gifProperties) { img._animateGif(this._pInst); @@ -446,11 +429,7 @@ class Renderer2D extends Renderer { const tint = this.states.tint._getRGBA([255, 255, 255, 255]); - if ( - tint[0] < 255 || - tint[1] < 255 || - tint[2] < 255 - ) { + if (tint[0] < 255 || tint[1] < 255 || tint[2] < 255) { // Color tint: we need to use the multiply blend mode to change the colors. // However, the canvas implementation of this destroys the alpha channel of // the image. To accommodate, we first get a version of the image with full @@ -494,7 +473,8 @@ class Renderer2D extends Renderer { ////////////////////////////////////////////// blendMode(mode) { - if (typeof mode === 'undefined') { // getter + if (typeof mode === 'undefined') { + // getter return this._cachedBlendMode; } if (mode === constants.SUBTRACT) { @@ -570,10 +550,7 @@ class Renderer2D extends Renderer { if (imgOrCol instanceof Graphics || imgOrCol instanceof Image) { this.drawingContext.save(); this.drawingContext.setTransform(1, 0, 0, 1, 0, 0); - this.drawingContext.scale( - this._pixelDensity, - this._pixelDensity - ); + this.drawingContext.scale(this._pixelDensity, this._pixelDensity); const width = imgOrCol.width; const height = imgOrCol.height; this.drawingContext.clearRect(x, y, width, height); @@ -585,9 +562,7 @@ class Renderer2D extends Renderer { a = 0; let idx = 4 * - (y * - this._pixelDensity * - (this.width * this._pixelDensity) + + (y * this._pixelDensity * (this.width * this._pixelDensity) + x * this._pixelDensity); if (!this.imageData) { this.loadPixels(); @@ -623,9 +598,7 @@ class Renderer2D extends Renderer { // loop over idx = 4 * - ((y * this._pixelDensity + j) * - this.width * - this._pixelDensity + + ((y * this._pixelDensity + j) * this.width * this._pixelDensity + (x * this._pixelDensity + i)); this.pixels[idx] = r; this.pixels[idx + 1] = g; @@ -676,20 +649,11 @@ class Renderer2D extends Renderer { arc(x, y, w, h, start, stop, mode) { const shape = new p5.Shape({ position: new p5.Vector(0, 0) }); shape.beginShape(); - shape.arcPrimitive( - x, - y, - w, - h, - start, - stop, - mode - ); + shape.arcPrimitive(x, y, w, h, start, stop, mode); shape.endShape(); this.drawShape(shape); return this; - } ellipse(args) { @@ -700,7 +664,7 @@ class Renderer2D extends Renderer { const shape = new p5.Shape({ position: new p5.Vector(0, 0) }); shape.beginShape(); - shape.ellipsePrimitive(x,y,w,h); + shape.ellipsePrimitive(x, y, w, h); shape.endShape(); this.drawShape(shape); return this; @@ -777,7 +741,8 @@ class Renderer2D extends Renderer { ////////////////////////////////////////////// strokeCap(cap) { - if (typeof cap === 'undefined') { // getter + if (typeof cap === 'undefined') { + // getter return this.drawingContext.lineCap; } if ( @@ -791,7 +756,8 @@ class Renderer2D extends Renderer { } strokeJoin(join) { - if (typeof join === 'undefined') { // getter + if (typeof join === 'undefined') { + // getter return this.drawingContext.lineJoin; } if ( @@ -834,7 +800,10 @@ class Renderer2D extends Renderer { _getStroke() { if (!this.states._cachedStrokeStyle) { - this.states.setValue('_cachedStrokeStyle', this.drawingContext.strokeStyle); + this.states.setValue( + '_cachedStrokeStyle', + this.drawingContext.strokeStyle + ); } return this.states._cachedStrokeStyle; } @@ -863,10 +832,7 @@ class Renderer2D extends Renderer { resetMatrix() { this.drawingContext.setTransform(1, 0, 0, 1, 0, 0); - this.drawingContext.scale( - this._pixelDensity, - this._pixelDensity - ); + this.drawingContext.scale(this._pixelDensity, this._pixelDensity); return this; } @@ -951,7 +917,6 @@ class Renderer2D extends Renderer { } if (!this._clipping && states.fillColor) { - // if fill hasn't been set by user, use default text fill if (!states.fillSet) { this._setFill(DefaultFill); @@ -967,7 +932,8 @@ class Renderer2D extends Renderer { */ _positionLines(x, y, width, height, lines) { let { textLeading, textAlign } = this.states; - let adjustedX, lineData = new Array(lines.length); + let adjustedX, + lineData = new Array(lines.length); let adjustedW = typeof width === 'undefined' ? 0 : width; let adjustedH = typeof height === 'undefined' ? 0 : height; @@ -1002,10 +968,13 @@ class Renderer2D extends Renderer { } let { textLeading, textBaseline } = this.states; - let yOff = 0, numLines = dataArr.length; - let ydiff = height - (textLeading * (numLines - 1)); + let yOff = 0, + numLines = dataArr.length; + let ydiff = height - textLeading * (numLines - 1); - switch (textBaseline) { // drawingContext ? + switch ( + textBaseline // drawingContext ? + ) { case constants.TOP: break; // ?? case constants.BASELINE: @@ -1017,19 +986,21 @@ class Renderer2D extends Renderer { yOff = ydiff; break; case textCoreConstants.IDEOGRAPHIC: - console.warn('textBounds: IDEOGRAPHIC not yet supported for textBaseline'); // FES? + console.warn( + 'textBounds: IDEOGRAPHIC not yet supported for textBaseline' + ); // FES? break; case textCoreConstants.HANGING: console.warn('textBounds: HANGING not yet supported for textBaseline'); // FES? break; } - dataArr.forEach(ele => ele.y += yOff); + dataArr.forEach(ele => (ele.y += yOff)); return dataArr; } } -function renderer2D(p5, fn){ +function renderer2D(p5, fn) { /** * p5.Renderer2D * The 2D graphics canvas renderer class. @@ -1039,8 +1010,10 @@ function renderer2D(p5, fn){ p5.Renderer2D = Renderer2D; p5.renderers[constants.P2D] = Renderer2D; p5.renderers['p2d-p3'] = new Proxy(Renderer2D, { - construct(target, [pInst, w, h, isMainCanvas, elt]){ - return new target(pInst, w, h, isMainCanvas, elt, { colorSpace: 'display-p3' }); + construct(target, [pInst, w, h, isMainCanvas, elt]) { + return new target(pInst, w, h, isMainCanvas, elt, { + colorSpace: 'display-p3' + }); } }); } diff --git a/src/core/p5.Renderer3D.js b/src/core/p5.Renderer3D.js index 363773befc..eb1f3e80b1 100644 --- a/src/core/p5.Renderer3D.js +++ b/src/core/p5.Renderer3D.js @@ -3,36 +3,36 @@ * @for p5 */ -import * as constants from "../core/constants"; -import { Graphics } from "../core/p5.Graphics"; +import * as constants from '../core/constants'; +import { Graphics } from '../core/p5.Graphics'; import { Renderer } from './p5.Renderer'; -import GeometryBuilder from "../webgl/GeometryBuilder"; -import { Matrix } from "../math/p5.Matrix"; -import { Camera } from "../webgl/p5.Camera"; -import { Vector } from "../math/p5.Vector"; -import { ShapeBuilder } from "../webgl/ShapeBuilder"; -import { GeometryBufferCache } from "../webgl/GeometryBufferCache"; -import { filterParamDefaults } from "../image/const"; -import { PrimitiveToVerticesConverter } from "../shape/custom_shapes"; -import { Color } from "../color/p5.Color"; -import { Element } from "../dom/p5.Element"; -import { Framebuffer } from "../webgl/p5.Framebuffer"; -import { DataArray } from "../webgl/p5.DataArray"; -import { textCoreConstants } from "../type/textCore"; -import { RenderBuffer } from "../webgl/p5.RenderBuffer"; -import { Image } from "../image/p5.Image"; -import { Texture } from "../webgl/p5.Texture"; -import { makeFilterShader } from "../core/filterShaders"; -import { getStrokeDefs } from "../webgl/enums"; - -const { STROKE_CAP_ENUM, STROKE_JOIN_ENUM } = getStrokeDefs(()=>""); +import GeometryBuilder from '../webgl/GeometryBuilder'; +import { Matrix } from '../math/p5.Matrix'; +import { Camera } from '../webgl/p5.Camera'; +import { Vector } from '../math/p5.Vector'; +import { ShapeBuilder } from '../webgl/ShapeBuilder'; +import { GeometryBufferCache } from '../webgl/GeometryBufferCache'; +import { filterParamDefaults } from '../image/const'; +import { PrimitiveToVerticesConverter } from '../shape/custom_shapes'; +import { Color } from '../color/p5.Color'; +import { Element } from '../dom/p5.Element'; +import { Framebuffer } from '../webgl/p5.Framebuffer'; +import { DataArray } from '../webgl/p5.DataArray'; +import { textCoreConstants } from '../type/textCore'; +import { RenderBuffer } from '../webgl/p5.RenderBuffer'; +import { Image } from '../image/p5.Image'; +import { Texture } from '../webgl/p5.Texture'; +import { makeFilterShader } from '../core/filterShaders'; +import { getStrokeDefs } from '../webgl/enums'; + +const { STROKE_CAP_ENUM, STROKE_JOIN_ENUM } = getStrokeDefs(() => ''); export class Renderer3D extends Renderer { constructor(pInst, w, h, isMainCanvas, elt) { super(pInst, w, h, isMainCanvas); // Create new canvas - this.canvas = this.elt = elt || document.createElement("canvas"); + this.canvas = this.elt = elt || document.createElement('canvas'); this.contextReady = this.setupContext(); if (this._isMainCanvas) { @@ -41,10 +41,10 @@ export class Renderer3D extends Renderer { this._pInst.canvas = this.canvas; } else { // hide if offscreen buffer by default - this.canvas.style.display = "none"; + this.canvas.style.display = 'none'; } - this.elt.id = "defaultCanvas0"; - this.elt.classList.add("p5Canvas"); + this.elt.id = 'defaultCanvas0'; + this.elt.classList.add('p5Canvas'); // Set and return p5.Element this.wrappedElt = new Element(this.elt, this._pInst); @@ -56,7 +56,7 @@ export class Renderer3D extends Renderer { get() { return this.wrappedElt[p]; } - }) + }); } } @@ -80,12 +80,12 @@ export class Renderer3D extends Renderer { this._pInst._userNode.appendChild(this.elt); } else { //create main element - if (document.getElementsByTagName("main").length === 0) { - let m = document.createElement("main"); + if (document.getElementsByTagName('main').length === 0) { + let m = document.createElement('main'); document.body.appendChild(m); } //append canvas to main - document.getElementsByTagName("main")[0].appendChild(this.elt); + document.getElementsByTagName('main')[0].appendChild(this.elt); } this.isP3D = true; //lets us know we're in 3d mode @@ -258,83 +258,87 @@ export class Renderer3D extends Renderer { fill: [ new RenderBuffer( 3, - "vertices", - "vertexBuffer", - "aPosition", + 'vertices', + 'vertexBuffer', + 'aPosition', this, this._vToNArray ), new RenderBuffer( 3, - "vertexNormals", - "normalBuffer", - "aNormal", + 'vertexNormals', + 'normalBuffer', + 'aNormal', this, this._vToNArray ), new RenderBuffer( 4, - "vertexColors", - "colorBuffer", - "aVertexColor", + 'vertexColors', + 'colorBuffer', + 'aVertexColor', this - ).default((geometry) => geometry.vertices.flatMap(() => [-1, -1, -1, -1])), + ).default(geometry => + geometry.vertices.flatMap(() => [-1, -1, -1, -1]) + ), new RenderBuffer( 3, - "vertexAmbients", - "ambientBuffer", - "aAmbientColor", + 'vertexAmbients', + 'ambientBuffer', + 'aAmbientColor', this ), - new RenderBuffer(2, "uvs", "uvBuffer", "aTexCoord", this, (arr) => + new RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, arr => arr.flat() - ), + ) ], stroke: [ new RenderBuffer( 4, - "lineVertexColors", - "lineColorBuffer", - "aVertexColor", + 'lineVertexColors', + 'lineColorBuffer', + 'aVertexColor', this - ).default((geometry) => geometry.lineVertices.flatMap(() => [-1, -1, -1, -1])), + ).default(geometry => + geometry.lineVertices.flatMap(() => [-1, -1, -1, -1]) + ), new RenderBuffer( 3, - "lineVertices", - "lineVerticesBuffer", - "aPosition", + 'lineVertices', + 'lineVerticesBuffer', + 'aPosition', this ), new RenderBuffer( 3, - "lineTangentsIn", - "lineTangentsInBuffer", - "aTangentIn", + 'lineTangentsIn', + 'lineTangentsInBuffer', + 'aTangentIn', this ), new RenderBuffer( 3, - "lineTangentsOut", - "lineTangentsOutBuffer", - "aTangentOut", + 'lineTangentsOut', + 'lineTangentsOutBuffer', + 'aTangentOut', this ), - new RenderBuffer(1, "lineSides", "lineSidesBuffer", "aSide", this), + new RenderBuffer(1, 'lineSides', 'lineSidesBuffer', 'aSide', this) ], text: [ new RenderBuffer( 3, - "vertices", - "vertexBuffer", - "aPosition", + 'vertices', + 'vertexBuffer', + 'aPosition', this, this._vToNArray ), - new RenderBuffer(2, "uvs", "uvBuffer", "aTexCoord", this, (arr) => + new RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, arr => arr.flat() - ), + ) ], - user: [], + user: [] }; } @@ -351,14 +355,14 @@ export class Renderer3D extends Renderer { const prevStyle = { position: this.canvas.style.position, top: this.canvas.style.top, - left: this.canvas.style.left, + left: this.canvas.style.left }; if (isPGraphics) { // Handle PGraphics: remove and recreate the canvas const pg = this._pInst; pg.canvas.parentNode.removeChild(pg.canvas); - pg.canvas = document.createElement("canvas"); + pg.canvas = document.createElement('canvas'); const node = pg._pInst._userNode || document.body; node.appendChild(pg.canvas); Element.call(pg, pg.canvas, pg._pInst); @@ -371,7 +375,7 @@ export class Renderer3D extends Renderer { if (c) { c.parentNode.removeChild(c); } - c = document.createElement("canvas"); + c = document.createElement('canvas'); c.id = defaultId; // Attach the new canvas to the correct parent node if (this._pInst._userNode) { @@ -400,10 +404,10 @@ export class Renderer3D extends Renderer { renderer._applyDefaults(); if (renderer.contextReady) { - await renderer.contextReady + await renderer.contextReady; } - if (typeof callback === "function") { + if (typeof callback === 'function') { //setTimeout with 0 forces the task to the back of the queue, this ensures that //we finish switching out the renderer setTimeout(() => { @@ -438,7 +442,7 @@ export class Renderer3D extends Renderer { beginGeometry() { if (this.geometryBuilder) { throw new Error( - "It looks like `beginGeometry()` is being called while another p5.Geometry is already being build." + 'It looks like `beginGeometry()` is being called while another p5.Geometry is already being build.' ); } this.geometryBuilder = new GeometryBuilder(this); @@ -458,7 +462,7 @@ export class Renderer3D extends Renderer { endGeometry() { if (!this.geometryBuilder) { throw new Error( - "Make sure you call beginGeometry() before endGeometry()!" + 'Make sure you call beginGeometry() before endGeometry()!' ); } const geometry = this.geometryBuilder.finish(); @@ -503,13 +507,13 @@ export class Renderer3D extends Renderer { if (d === undefined) { return this.states.curveDetail; } else { - this.states.setValue("curveDetail", d); + this.states.setValue('curveDetail', d); } } drawShape(shape) { const visitor = new PrimitiveToVerticesConverter({ - curveDetail: this.states.curveDetail, + curveDetail: this.states.curveDetail }); shape.accept(visitor); this.shapeBuilder.constructFromContours(shape, visitor.contours); @@ -540,9 +544,9 @@ export class Renderer3D extends Renderer { normal(xorv, y, z) { if (xorv instanceof Vector) { - this.states.setValue("_currentNormal", xorv); + this.states.setValue('_currentNormal', xorv); } else { - this.states.setValue("_currentNormal", new Vector(xorv, y, z)); + this.states.setValue('_currentNormal', new Vector(xorv, y, z)); } this.updateShapeVertexProperties(); } @@ -600,8 +604,8 @@ export class Renderer3D extends Renderer { } _drawFills(geometry, { count, mode } = {}) { - this._useVertexColor = geometry.vertexColors.length > 0 && - !geometry.vertexColors.isDefault; + this._useVertexColor = + geometry.vertexColors.length > 0 && !geometry.vertexColors.isDefault; const shader = !this._drawingFilter && this.states.userFillShader @@ -629,7 +633,6 @@ export class Renderer3D extends Renderer { } _drawStrokes(geometry, { count } = {}) { - this._useLineColor = geometry.vertexStrokeColors.length > 0; const shader = this._getStrokeShader(); @@ -649,7 +652,7 @@ export class Renderer3D extends Renderer { geometry.hasStrokeTransparency() ); - this._drawBuffers(geometry, {count}) + this._drawBuffers(geometry, { count }); shader.unbindShader(); } @@ -664,12 +667,12 @@ export class Renderer3D extends Renderer { if (adjustedLength > geometry.vertices.length) { this._pInst.constructor._friendlyError( `One of the geometries has a custom vertex property '${prop.getName()}' with more values than vertices. This is probably caused by directly using the Geometry.vertexProperty() method.`, - "vertexProperty()" + 'vertexProperty()' ); } else if (adjustedLength < geometry.vertices.length) { this._pInst.constructor._friendlyError( `One of the geometries has a custom vertex property '${prop.getName()}' with fewer values than vertices. This is probably caused by directly using the Geometry.vertexProperty() method.`, - "vertexProperty()" + 'vertexProperty()' ); } } @@ -680,7 +683,7 @@ export class Renderer3D extends Renderer { _drawGeometryScaled(model, scaleX, scaleY, scaleZ) { let originalModelMatrix = this.states.uModelMatrix; - this.states.setValue("uModelMatrix", this.states.uModelMatrix.clone()); + this.states.setValue('uModelMatrix', this.states.uModelMatrix.clone()); try { this.states.uModelMatrix.scale(scaleX, scaleY, scaleZ); @@ -690,42 +693,42 @@ export class Renderer3D extends Renderer { this._drawGeometry(model); } } finally { - this.states.setValue("uModelMatrix", originalModelMatrix); + this.states.setValue('uModelMatrix', originalModelMatrix); } } _update() { // reset model view and apply initial camera transform // (containing only look at info; no projection). - this.states.setValue("uModelMatrix", this.states.uModelMatrix.clone()); + this.states.setValue('uModelMatrix', this.states.uModelMatrix.clone()); this.states.uModelMatrix.reset(); - this.states.setValue("uViewMatrix", this.states.uViewMatrix.clone()); + this.states.setValue('uViewMatrix', this.states.uViewMatrix.clone()); this.states.uViewMatrix.set(this.states.curCamera.cameraMatrix); // reset light data for new frame. - this.states.setValue("ambientLightColors", []); - this.states.setValue("specularColors", [1, 1, 1]); + this.states.setValue('ambientLightColors', []); + this.states.setValue('specularColors', [1, 1, 1]); - this.states.setValue("directionalLightDirections", []); - this.states.setValue("directionalLightDiffuseColors", []); - this.states.setValue("directionalLightSpecularColors", []); + this.states.setValue('directionalLightDirections', []); + this.states.setValue('directionalLightDiffuseColors', []); + this.states.setValue('directionalLightSpecularColors', []); - this.states.setValue("pointLightPositions", []); - this.states.setValue("pointLightDiffuseColors", []); - this.states.setValue("pointLightSpecularColors", []); + this.states.setValue('pointLightPositions', []); + this.states.setValue('pointLightDiffuseColors', []); + this.states.setValue('pointLightSpecularColors', []); - this.states.setValue("spotLightPositions", []); - this.states.setValue("spotLightDirections", []); - this.states.setValue("spotLightDiffuseColors", []); - this.states.setValue("spotLightSpecularColors", []); - this.states.setValue("spotLightAngle", []); - this.states.setValue("spotLightConc", []); + this.states.setValue('spotLightPositions', []); + this.states.setValue('spotLightDirections', []); + this.states.setValue('spotLightDiffuseColors', []); + this.states.setValue('spotLightSpecularColors', []); + this.states.setValue('spotLightAngle', []); + this.states.setValue('spotLightConc', []); - this.states.setValue("enableLighting", false); + this.states.setValue('enableLighting', false); //reset tint value for new frame - this.states.setValue("tint", new Color([1,1,1,1])); + this.states.setValue('tint', new Color([1, 1, 1, 1])); //Clear depth every frame this._resetBuffersBeforeDraw(); @@ -793,8 +796,9 @@ export class Renderer3D extends Renderer { const modelViewMatrix = modelMatrix.copy().mult(viewMatrix); const modelViewProjectionMatrix = modelViewMatrix.mult(projectionMatrix); - const worldToScreenMatrix = modelViewProjectionMatrix - .mult(projectedToScreenMatrix); + const worldToScreenMatrix = modelViewProjectionMatrix.mult( + projectedToScreenMatrix + ); return worldToScreenMatrix; } @@ -874,13 +878,13 @@ export class Renderer3D extends Renderer { ...super.getCommonVertexProperties(), stroke: this.states.strokeColor, fill: this.states.fillColor, - normal: this.states._currentNormal, + normal: this.states._currentNormal }; } getSupportedIndividualVertexProperties() { return { - textureCoordinates: true, + textureCoordinates: true }; } @@ -933,7 +937,10 @@ export class Renderer3D extends Renderer { // Need to store multiple in case user calls different filters, // eg. filter(BLUR) then filter(GRAY) if (!(operation in this.defaultFilterShaders)) { - this.defaultFilterShaders[operation] = this._makeFilterShader(fbo.renderer, operation); + this.defaultFilterShaders[operation] = this._makeFilterShader( + fbo.renderer, + operation + ); } this.states.setValue( 'filterShader', @@ -956,7 +963,7 @@ export class Renderer3D extends Renderer { let texelSize = [ 1 / (target.width * target.pixelDensity()), - 1 / (target.height * target.pixelDensity()), + 1 / (target.height * target.pixelDensity()) ]; // apply blur shader with multiple passes. @@ -975,7 +982,7 @@ export class Renderer3D extends Renderer { this.states.filterShader.setUniform('texelSize', texelSize); this.states.filterShader.setUniform('canvasSize', [ target.width, - target.height, + target.height ]); this.states.filterShader.setUniform( 'radius', @@ -1014,7 +1021,7 @@ export class Renderer3D extends Renderer { this.states.filterShader.setUniform('texelSize', texelSize); this.states.filterShader.setUniform('canvasSize', [ target.width, - target.height, + target.height ]); // filterParameter uniform only used for POSTERIZE, and THRESHOLD // but shouldn't hurt to always set @@ -1167,7 +1174,7 @@ export class Renderer3D extends Renderer { format: constants.UNSIGNED_BYTE, useDepth: this._pInst._glAttributes.depth, depthFormat: constants.UNSIGNED_INT, - antialias: this._pInst._glAttributes.antialias, + antialias: this._pInst._glAttributes.antialias }); } return this._tempFramebuffer; @@ -1194,7 +1201,7 @@ export class Renderer3D extends Renderer { const props = {}; for (const key in this.drawingContext) { const val = this.drawingContext[key]; - if (typeof val !== "object" && typeof val !== "function") { + if (typeof val !== 'object' && typeof val !== 'function') { props[key] = val; } } @@ -1219,7 +1226,7 @@ export class Renderer3D extends Renderer { } //resize pixels buffer - if (typeof this.pixels !== "undefined") { + if (typeof this.pixels !== 'undefined') { this._createPixelsArray(); } @@ -1242,7 +1249,7 @@ export class Renderer3D extends Renderer { } applyMatrix(a, b, c, d, e, f) { - this.states.setValue("uModelMatrix", this.states.uModelMatrix.clone()); + this.states.setValue('uModelMatrix', this.states.uModelMatrix.clone()); if (arguments.length === 16) { // this.states.uModelMatrix.apply(arguments); Matrix.prototype.apply.apply(this.states.uModelMatrix, arguments); @@ -1263,7 +1270,7 @@ export class Renderer3D extends Renderer { e, f, 0, - 1, + 1 ]); } } @@ -1283,7 +1290,7 @@ export class Renderer3D extends Renderer { y = x.y; x = x.x; } - this.states.setValue("uModelMatrix", this.states.uModelMatrix.clone()); + this.states.setValue('uModelMatrix', this.states.uModelMatrix.clone()); this.states.uModelMatrix.translate([x, y, z]); return this; } @@ -1297,16 +1304,16 @@ export class Renderer3D extends Renderer { * @chainable */ scale(x, y, z) { - this.states.setValue("uModelMatrix", this.states.uModelMatrix.clone()); + this.states.setValue('uModelMatrix', this.states.uModelMatrix.clone()); this.states.uModelMatrix.scale(x, y, z); return this; } rotate(rad, axis) { - if (typeof axis === "undefined") { + if (typeof axis === 'undefined') { return this.rotateZ(rad); } - this.states.setValue("uModelMatrix", this.states.uModelMatrix.clone()); + this.states.setValue('uModelMatrix', this.states.uModelMatrix.clone()); Matrix.prototype.rotate4x4.apply(this.states.uModelMatrix, arguments); return this; } @@ -1327,10 +1334,10 @@ export class Renderer3D extends Renderer { } push() { - super.push() - const saved = !!(this.states.textFont?.font); + super.push(); + const saved = !!this.states.textFont?.font; if (saved) { - this.textDrawingContext().save() + this.textDrawingContext().save(); } this._textContextSavedStack.push(saved); } @@ -1343,16 +1350,16 @@ export class Renderer3D extends Renderer { this._clearClip(); } if (this._textContextSavedStack.pop()) { - this.textDrawingContext().restore() + this.textDrawingContext().restore(); } super.pop(...args); this._applyStencilTestIfClipping(); } resetMatrix() { - this.states.setValue("uModelMatrix", this.states.uModelMatrix.clone()); + this.states.setValue('uModelMatrix', this.states.uModelMatrix.clone()); this.states.uModelMatrix.reset(); - this.states.setValue("uViewMatrix", this.states.uViewMatrix.clone()); + this.states.setValue('uViewMatrix', this.states.uViewMatrix.clone()); this.states.uViewMatrix.set(this.states.curCamera.cameraMatrix); return this; } @@ -1437,45 +1444,45 @@ export class Renderer3D extends Renderer { const modelViewMatrix = modelMatrix.copy().mult(viewMatrix); shader.setUniform( - "uPerspective", + 'uPerspective', this.states.curCamera.useLinePerspective ? 1 : 0 ); - shader.setUniform("uViewMatrix", viewMatrix.mat4); - shader.setUniform("uProjectionMatrix", projectionMatrix.mat4); - shader.setUniform("uModelMatrix", modelMatrix.mat4); - shader.setUniform("uModelViewMatrix", modelViewMatrix.mat4); + shader.setUniform('uViewMatrix', viewMatrix.mat4); + shader.setUniform('uProjectionMatrix', projectionMatrix.mat4); + shader.setUniform('uModelMatrix', modelMatrix.mat4); + shader.setUniform('uModelViewMatrix', modelViewMatrix.mat4); if (shader.uniforms.uModelViewProjectionMatrix) { const modelViewProjectionMatrix = modelViewMatrix.copy(); modelViewProjectionMatrix.mult(projectionMatrix); shader.setUniform( - "uModelViewProjectionMatrix", + 'uModelViewProjectionMatrix', modelViewProjectionMatrix.mat4 ); } if (shader.uniforms.uNormalMatrix) { this.scratchMat3.inverseTranspose4x4(modelViewMatrix); - shader.setUniform("uNormalMatrix", this.scratchMat3.mat3); + shader.setUniform('uNormalMatrix', this.scratchMat3.mat3); } if (shader.uniforms.uModelNormalMatrix) { this.scratchMat3.inverseTranspose4x4(this.states.uModelMatrix); - shader.setUniform("uModelNormalMatrix", this.scratchMat3.mat3); + shader.setUniform('uModelNormalMatrix', this.scratchMat3.mat3); } if (shader.uniforms.uCameraNormalMatrix) { this.scratchMat3.inverseTranspose4x4(this.states.uViewMatrix); - shader.setUniform("uCameraNormalMatrix", this.scratchMat3.mat3); + shader.setUniform('uCameraNormalMatrix', this.scratchMat3.mat3); } - shader.setUniform("uViewport", this._viewport); + shader.setUniform('uViewport', this._viewport); } _setStrokeUniforms(strokeShader) { // set the uniform values - strokeShader.setUniform("uSimpleLines", this._simpleLines); - strokeShader.setUniform("uUseLineColor", this._useLineColor); - strokeShader.setUniform("uMaterialColor", this.states.curStrokeColor); - strokeShader.setUniform("uStrokeWeight", this.states.strokeWeight); - strokeShader.setUniform("uStrokeCap", STROKE_CAP_ENUM[this.curStrokeCap]); + strokeShader.setUniform('uSimpleLines', this._simpleLines); + strokeShader.setUniform('uUseLineColor', this._useLineColor); + strokeShader.setUniform('uMaterialColor', this.states.curStrokeColor); + strokeShader.setUniform('uStrokeWeight', this.states.strokeWeight); + strokeShader.setUniform('uStrokeCap', STROKE_CAP_ENUM[this.curStrokeCap]); strokeShader.setUniform( - "uStrokeJoin", + 'uStrokeJoin', STROKE_JOIN_ENUM[this.curStrokeJoin] ); } @@ -1493,9 +1500,9 @@ export class Renderer3D extends Renderer { } // TODO: optimize - fillShader.setUniform("uUseVertexColor", this._useVertexColor); - fillShader.setUniform("uMaterialColor", this.states.curFillColor); - fillShader.setUniform("isTexture", !!this.states._tex); + fillShader.setUniform('uUseVertexColor', this._useVertexColor); + fillShader.setUniform('uMaterialColor', this.states.curFillColor); + fillShader.setUniform('isTexture', !!this.states._tex); // We need to explicitly set uSampler back to an empty texture here. // In general, we record the last set texture so we can re-apply it // the next time a shader is used. However, the texture() function @@ -1503,55 +1510,55 @@ export class Renderer3D extends Renderer { // been cleared, we also need to clear the value in uSampler to match. this._settingFillUniforms = true; if (this.states._tex || !fillShader._userSetSampler) { - fillShader.setUniform("uSampler", this.states._tex || empty); + fillShader.setUniform('uSampler', this.states._tex || empty); } this._settingFillUniforms = false; fillShader.setUniform( - "uTint", + 'uTint', this.states.tint?._getRGBA([255, 255, 255, 255]) ?? [255, 255, 255, 255] ); - fillShader.setUniform("uHasSetAmbient", this.states._hasSetAmbient); - fillShader.setUniform("uAmbientMatColor", this.states.curAmbientColor); - fillShader.setUniform("uSpecularMatColor", this.mixedSpecularColor); - fillShader.setUniform("uEmissiveMatColor", this.states.curEmissiveColor); - fillShader.setUniform("uSpecular", this.states._useSpecularMaterial); - fillShader.setUniform("uEmissive", this.states._useEmissiveMaterial); - fillShader.setUniform("uShininess", this.states._useShininess); - fillShader.setUniform("uMetallic", this.states._useMetalness); + fillShader.setUniform('uHasSetAmbient', this.states._hasSetAmbient); + fillShader.setUniform('uAmbientMatColor', this.states.curAmbientColor); + fillShader.setUniform('uSpecularMatColor', this.mixedSpecularColor); + fillShader.setUniform('uEmissiveMatColor', this.states.curEmissiveColor); + fillShader.setUniform('uSpecular', this.states._useSpecularMaterial); + fillShader.setUniform('uEmissive', this.states._useEmissiveMaterial); + fillShader.setUniform('uShininess', this.states._useShininess); + fillShader.setUniform('uMetallic', this.states._useMetalness); this._setImageLightUniforms(fillShader); - fillShader.setUniform("uUseLighting", this.states.enableLighting); + fillShader.setUniform('uUseLighting', this.states.enableLighting); const pointLightCount = this.states.pointLightDiffuseColors.length / 3; - fillShader.setUniform("uPointLightCount", pointLightCount); + fillShader.setUniform('uPointLightCount', pointLightCount); fillShader.setUniform( - "uPointLightLocation", + 'uPointLightLocation', this.states.pointLightPositions ); fillShader.setUniform( - "uPointLightDiffuseColors", + 'uPointLightDiffuseColors', this.states.pointLightDiffuseColors ); fillShader.setUniform( - "uPointLightSpecularColors", + 'uPointLightSpecularColors', this.states.pointLightSpecularColors ); const directionalLightCount = this.states.directionalLightDiffuseColors.length / 3; - fillShader.setUniform("uDirectionalLightCount", directionalLightCount); + fillShader.setUniform('uDirectionalLightCount', directionalLightCount); fillShader.setUniform( - "uLightingDirection", + 'uLightingDirection', this.states.directionalLightDirections ); fillShader.setUniform( - "uDirectionalDiffuseColors", + 'uDirectionalDiffuseColors', this.states.directionalLightDiffuseColors ); fillShader.setUniform( - "uDirectionalSpecularColors", + 'uDirectionalSpecularColors', this.states.directionalLightSpecularColors ); @@ -1569,33 +1576,33 @@ export class Renderer3D extends Renderer { } } } - fillShader.setUniform("uAmbientColor", mixedAmbientLight); + fillShader.setUniform('uAmbientColor', mixedAmbientLight); const spotLightCount = this.states.spotLightDiffuseColors.length / 3; - fillShader.setUniform("uSpotLightCount", spotLightCount); - fillShader.setUniform("uSpotLightAngle", this.states.spotLightAngle); - fillShader.setUniform("uSpotLightConc", this.states.spotLightConc); + fillShader.setUniform('uSpotLightCount', spotLightCount); + fillShader.setUniform('uSpotLightAngle', this.states.spotLightAngle); + fillShader.setUniform('uSpotLightConc', this.states.spotLightConc); fillShader.setUniform( - "uSpotLightDiffuseColors", + 'uSpotLightDiffuseColors', this.states.spotLightDiffuseColors ); fillShader.setUniform( - "uSpotLightSpecularColors", + 'uSpotLightSpecularColors', this.states.spotLightSpecularColors ); - fillShader.setUniform("uSpotLightLocation", this.states.spotLightPositions); + fillShader.setUniform('uSpotLightLocation', this.states.spotLightPositions); fillShader.setUniform( - "uSpotLightDirection", + 'uSpotLightDirection', this.states.spotLightDirections ); fillShader.setUniform( - "uConstantAttenuation", + 'uConstantAttenuation', this.states.constantAttenuation ); - fillShader.setUniform("uLinearAttenuation", this.states.linearAttenuation); + fillShader.setUniform('uLinearAttenuation', this.states.linearAttenuation); fillShader.setUniform( - "uQuadraticAttenuation", + 'uQuadraticAttenuation', this.states.quadraticAttenuation ); } @@ -1603,19 +1610,19 @@ export class Renderer3D extends Renderer { // getting called from _setFillUniforms _setImageLightUniforms(shader) { //set uniform values - shader.setUniform("uUseImageLight", this.states.activeImageLight != null); + shader.setUniform('uUseImageLight', this.states.activeImageLight != null); // true if (this.states.activeImageLight) { // this.states.activeImageLight has image as a key // look up the texture from the diffusedTexture map let diffusedLight = this.getDiffusedTexture(this.states.activeImageLight); - shader.setUniform("environmentMapDiffused", diffusedLight); + shader.setUniform('environmentMapDiffused', diffusedLight); let specularLight = this.getSpecularTexture(this.states.activeImageLight); - shader.setUniform("environmentMapSpecular", specularLight); + shader.setUniform('environmentMapSpecular', specularLight); } else { - shader.setUniform("environmentMapDiffused", this._getEmptyTexture()); - shader.setUniform("environmentMapSpecular", this._getEmptyTexture()); + shader.setUniform('environmentMapDiffused', this._getEmptyTexture()); + shader.setUniform('environmentMapSpecular', this._getEmptyTexture()); } } @@ -1681,8 +1688,8 @@ export class Renderer3D extends Renderer { Float64Array, Int16Array, Uint16Array, - Uint32Array, - ].some((x) => arr instanceof x); + Uint32Array + ].some(x => arr instanceof x); } /** @@ -1694,7 +1701,7 @@ export class Renderer3D extends Renderer { * [1, 2, 3, 4, 5, 6] */ _vToNArray(arr) { - return arr.flatMap((item) => [item.x, item.y, item.z]); + return arr.flatMap(item => [item.x, item.y, item.z]); } /////////////////////////////// @@ -1727,8 +1734,10 @@ export class Renderer3D extends Renderer { _positionLines(x, y, width, height, lines) { let { textLeading, textAlign } = this.states; const widths = lines.map(line => this._fontWidthSingle(line)); - let adjustedX, lineData = new Array(lines.length); - let adjustedW = typeof width === 'undefined' ? Math.max(0, ...widths) : width; + let adjustedX, + lineData = new Array(lines.length); + let adjustedW = + typeof width === 'undefined' ? Math.max(0, ...widths) : width; let adjustedH = typeof height === 'undefined' ? 0 : height; for (let i = 0; i < lines.length; i++) { @@ -1739,10 +1748,8 @@ export class Renderer3D extends Renderer { adjustedX = x; break; case constants.CENTER: - adjustedX = x + - (adjustedW - widths[i]) / 2 - - adjustedW / 2 + - (width || 0) / 2; + adjustedX = + x + (adjustedW - widths[i]) / 2 - adjustedW / 2 + (width || 0) / 2; break; case constants.RIGHT: adjustedX = x + adjustedW - widths[i] - adjustedW + (width || 0); @@ -1759,12 +1766,14 @@ export class Renderer3D extends Renderer { return this._yAlignOffset(lineData, adjustedH); } - _verticalAlignFont = function() { + _verticalAlignFont = function () { const ctx = this.textDrawingContext(); const metrics = ctx.measureText('X'); - return -metrics.alphabeticBaseline || - (-metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent); - } + return ( + -metrics.alphabeticBaseline || + -metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent + ); + }; _yAlignOffset(dataArr, height) { if (typeof height === 'undefined') { @@ -1772,17 +1781,23 @@ export class Renderer3D extends Renderer { } let { textLeading, textBaseline, textSize, textFont } = this.states; - let yOff = 0, numLines = dataArr.length; - let totalHeight = textSize * numLines + - ((textLeading - textSize) * (numLines - 1)); - switch (textBaseline) { // drawingContext ? + let yOff = 0, + numLines = dataArr.length; + let totalHeight = + textSize * numLines + (textLeading - textSize) * (numLines - 1); + switch ( + textBaseline // drawingContext ? + ) { case constants.TOP: yOff = this._verticalAlignFont(); break; case constants.BASELINE: break; case textCoreConstants._CTX_MIDDLE: - yOff = (-totalHeight + textSize + (height || 0)) / 2 + this._verticalAlignFont() + this._middleAlignOffset(); + yOff = + (-totalHeight + textSize + (height || 0)) / 2 + + this._verticalAlignFont() + + this._middleAlignOffset(); break; case constants.BOTTOM: yOff = -(totalHeight - textSize) + (height || 0); @@ -1791,7 +1806,7 @@ export class Renderer3D extends Renderer { console.warn(`${textBaseline} is not supported in WebGL mode.`); // FES? break; } - dataArr.forEach(ele => ele.y += yOff); + dataArr.forEach(ele => (ele.y += yOff)); return dataArr; } @@ -1825,17 +1840,17 @@ export class Renderer3D extends Renderer { newFramebuffer = new Framebuffer(this, { width, height, - density: 1, + density: 1 }); // create framebuffer is like making a new sketch, all functions on main // sketch it would be available on framebuffer if (!this.diffusedShader) { - this.diffusedShader = this._createImageLightShader("diffused"); + this.diffusedShader = this._createImageLightShader('diffused'); } newFramebuffer.draw(() => { this.shader(this.diffusedShader); this._setImageLightShaderUniforms(this.diffusedShader, input); - this.states.setValue("strokeColor", null); + this.states.setValue('strokeColor', null); this.noLights(); this.plane(width, height); }); @@ -1867,7 +1882,7 @@ export class Renderer3D extends Renderer { let count = Math.floor(Math.log2(size)) + 1; // Actual number of mip levels from size down to 1x1 if (!this.specularShader) { - this.specularShader = this._createImageLightShader("specular"); + this.specularShader = this._createImageLightShader('specular'); } // Prepare mipmap level accumulator @@ -1876,7 +1891,7 @@ export class Renderer3D extends Renderer { const framebuffer = new Framebuffer(this, { width: size, height: size, - density: 1, + density: 1 }); // currently only 8 levels @@ -1895,9 +1910,9 @@ export class Renderer3D extends Renderer { this._setImageLightShaderUniforms( this.specularShader, input, - roughness, + roughness ); - this.states.setValue("strokeColor", null); + this.states.setValue('strokeColor', null); this.noLights(); this.plane(w, w); }); @@ -1922,47 +1937,50 @@ export class Renderer3D extends Renderer { _getSphereMapping(img) { if (!this.sphereMapping) { const p5 = this._pInst; - this.sphereMapping = this.baseFilterShader().modify(({ p5 }) => { - const uEnvMap = p5.uniformTexture('uEnvMap'); - const uFovY = p5.uniformFloat('uFovY'); - const uAspect = p5.uniformFloat('uAspect'); - // Hack: we don't have matrix uniforms yet; use three vectors - const uN1 = p5.uniformVec3('uN1'); - const uN2 = p5.uniformVec3('uN2'); - const uN3 = p5.uniformVec3('uN3'); - p5.getColor((inputs) => { - const uFovX = uFovY * uAspect; - const angleY = p5.mix(uFovY/2.0, -uFovY/2.0, inputs.texCoord.y); - const angleX = p5.mix(uFovX/2.0, -uFovX/2.0, inputs.texCoord.x); - let rotatedNormal = p5.normalize([angleX, angleY, 1]); - rotatedNormal = [ - // Don't mind me, just doing matrix vector multiplication... - p5.dot(rotatedNormal, uN1), - p5.dot(rotatedNormal, uN2), - p5.dot(rotatedNormal, uN3), - ]; - const temp = rotatedNormal.z; - rotatedNormal.z = rotatedNormal.x; - rotatedNormal.x = -temp; - const suv = [ - p5.atan(rotatedNormal.z, rotatedNormal.x) / (2.0 * p5.PI) + 0.5, - 0.5 + 0.5 * (-rotatedNormal.y) - ]; - return p5.getTexture(uEnvMap, suv); - }) - }, { p5 }); + this.sphereMapping = this.baseFilterShader().modify( + ({ p5 }) => { + const uEnvMap = p5.uniformTexture('uEnvMap'); + const uFovY = p5.uniformFloat('uFovY'); + const uAspect = p5.uniformFloat('uAspect'); + // Hack: we don't have matrix uniforms yet; use three vectors + const uN1 = p5.uniformVec3('uN1'); + const uN2 = p5.uniformVec3('uN2'); + const uN3 = p5.uniformVec3('uN3'); + p5.getColor(inputs => { + const uFovX = uFovY * uAspect; + const angleY = p5.mix(uFovY / 2.0, -uFovY / 2.0, inputs.texCoord.y); + const angleX = p5.mix(uFovX / 2.0, -uFovX / 2.0, inputs.texCoord.x); + let rotatedNormal = p5.normalize([angleX, angleY, 1]); + rotatedNormal = [ + // Don't mind me, just doing matrix vector multiplication... + p5.dot(rotatedNormal, uN1), + p5.dot(rotatedNormal, uN2), + p5.dot(rotatedNormal, uN3) + ]; + const temp = rotatedNormal.z; + rotatedNormal.z = rotatedNormal.x; + rotatedNormal.x = -temp; + const suv = [ + p5.atan(rotatedNormal.z, rotatedNormal.x) / (2.0 * p5.PI) + 0.5, + 0.5 + 0.5 * -rotatedNormal.y + ]; + return p5.getTexture(uEnvMap, suv); + }); + }, + { p5 } + ); } this.scratchMat3.inverseTranspose4x4(this.states.uViewMatrix); this.scratchMat3.invert(this.scratchMat3); // uNMMatrix is 3x3 - this.sphereMapping.setUniform("uFovY", this.states.curCamera.cameraFOV); - this.sphereMapping.setUniform("uAspect", this.states.curCamera.aspectRatio); + this.sphereMapping.setUniform('uFovY', this.states.curCamera.cameraFOV); + this.sphereMapping.setUniform('uAspect', this.states.curCamera.aspectRatio); // Pass in the normal matrix as three vectors. TODO replace this with // an actual matrix uniform once we have those again. const m = this.scratchMat3.mat3; - this.sphereMapping.setUniform("uN1", [m[0], m[3], m[6]]); - this.sphereMapping.setUniform("uN2", [m[1], m[4], m[7]]); - this.sphereMapping.setUniform("uN3", [m[2], m[5], m[8]]); - this.sphereMapping.setUniform("uEnvMap", img); + this.sphereMapping.setUniform('uN1', [m[0], m[3], m[6]]); + this.sphereMapping.setUniform('uN2', [m[1], m[4], m[7]]); + this.sphereMapping.setUniform('uN3', [m[2], m[5], m[8]]); + this.sphereMapping.setUniform('uEnvMap', img); return this.sphereMapping; } @@ -1971,32 +1989,32 @@ export class Renderer3D extends Renderer { */ _createImageLightShader(type) { throw new Error( - "_createImageLightShader must be implemented by the renderer", + '_createImageLightShader must be implemented by the renderer' ); } _setImageLightShaderUniforms(shader, input, roughness) { - shader.setUniform("environmentMap", input); + shader.setUniform('environmentMap', input); if (roughness !== undefined) { - shader.setUniform("roughness", roughness); + shader.setUniform('roughness', roughness); } } _createMipmapTexture(levels) { - throw new Error("_createMipmapTexture must be implemented by the renderer"); + throw new Error('_createMipmapTexture must be implemented by the renderer'); } _prepareMipmapData(size, mipLevels) { - throw new Error("_prepareMipmapData must be implemented by the renderer"); + throw new Error('_prepareMipmapData must be implemented by the renderer'); } _accumulateMipLevel(framebuffer, mipmapData, mipLevel, width, height) { - throw new Error("_accumulateMipLevel must be implemented by the renderer"); + throw new Error('_accumulateMipLevel must be implemented by the renderer'); } _finalizeMipmapTexture(mipmapData) { throw new Error( - "_finalizeMipmapTexture must be implemented by the renderer", + '_finalizeMipmapTexture must be implemented by the renderer' ); } @@ -2008,12 +2026,13 @@ export class Renderer3D extends Renderer { } } -const webGPUAddonMessage = 'Add the WebGPU add-on to your project and pass WEBGPU as the last argument to createCanvas.'; +const webGPUAddonMessage = + 'Add the WebGPU add-on to your project and pass WEBGPU as the last argument to createCanvas.'; function renderer3D(p5, fn) { p5.Renderer3D = Renderer3D; - ShapeBuilder.prototype.friendlyErrorsDisabled = function() { + ShapeBuilder.prototype.friendlyErrorsDisabled = function () { return Boolean(p5.disableFriendlyErrors); }; @@ -2604,6 +2623,6 @@ function renderer3D(p5, fn) { export default renderer3D; -if (typeof p5 !== "undefined") { +if (typeof p5 !== 'undefined') { renderer3D(p5, p5.prototype); } diff --git a/src/core/reference.js b/src/core/reference.js index f912d871be..d2f6842fc6 100644 --- a/src/core/reference.js +++ b/src/core/reference.js @@ -1121,7 +1121,7 @@ * @property async_await * @example * // Click and drag the mouse to view the scene from different angles. - * + * * let shape; * * // Load the file and create a p5.Geometry object. diff --git a/src/core/rendering.js b/src/core/rendering.js index eab87bc4c8..255bff41b6 100644 --- a/src/core/rendering.js +++ b/src/core/rendering.js @@ -8,7 +8,7 @@ import * as constants from './constants'; import { Framebuffer } from '../webgl/p5.Framebuffer'; let renderers; -function rendering(p5, fn){ +function rendering(p5, fn) { // Extend additional renderers object to p5 class, new renderer can be similarly attached if (!p5.renderers) { p5.renderers = {}; @@ -129,22 +129,24 @@ function rendering(p5, fn){ let selectedRenderer = constants.P2D; // Check third argument whether it is renderer constants - if(Reflect.ownKeys(renderers).includes(renderer)){ + if (Reflect.ownKeys(renderers).includes(renderer)) { selectedRenderer = renderer; - }else{ + } else { args.unshift(renderer); } if (!renderers[selectedRenderer]) { if (selectedRenderer === constants.WEBGPU) { - p5.FES.log`To create a WEBGPU canvas, remember to add the WebGPU add-on to your project.`(); + p5.FES + .log`To create a WEBGPU canvas, remember to add the WebGPU add-on to your project.`(); } else { - p5.FES.log`We weren't able to find a renderer called ${selectedRenderer}.`(); + p5.FES + .log`We weren't able to find a renderer called ${selectedRenderer}.`(); } } // Init our graphics renderer - if(this._renderer) this._renderer.remove(); + if (this._renderer) this._renderer.remove(); this._renderer = new renderers[selectedRenderer](this, w, h, true, ...args); this._defaultGraphicsCreated = true; this._elements.push(this._renderer); @@ -153,7 +155,7 @@ function rendering(p5, fn){ // Make the renderer own `pixels` if (!Object.hasOwn(this, 'pixels')) { Object.defineProperty(this, 'pixels', { - get(){ + get() { return this._renderer?.pixels; } }); @@ -678,6 +680,6 @@ function rendering(p5, fn){ export default rendering; export { renderers }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { rendering(p5, p5.prototype); } diff --git a/src/core/structure.js b/src/core/structure.js index 6d17882f27..a65ce88ce7 100644 --- a/src/core/structure.js +++ b/src/core/structure.js @@ -4,7 +4,7 @@ * @for p5 */ -function structure(p5, fn){ +function structure(p5, fn) { /** * Stops the code in draw() from running repeatedly. * @@ -109,7 +109,7 @@ function structure(p5, fn){ * circle(x, y, 20); * } */ - fn.noLoop = function() { + fn.noLoop = function () { this._loop = false; }; @@ -194,7 +194,7 @@ function structure(p5, fn){ * circle(x, y, 20); * } */ - fn.loop = function() { + fn.loop = function () { if (!this._loop) { this._loop = true; if (this._setupDone) { @@ -240,7 +240,7 @@ function structure(p5, fn){ * } * } */ - fn.isLooping = function() { + fn.isLooping = function () { return this._loop; }; @@ -323,7 +323,7 @@ function structure(p5, fn){ * await redraw(3); * } */ - fn.redraw = async function(n) { + fn.redraw = async function (n) { if (this._inUserDraw || !this._setupDone) { return; } @@ -544,6 +544,6 @@ function structure(p5, fn){ export default structure; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { structure(p5, p5.prototype); } diff --git a/src/core/transform.js b/src/core/transform.js index 4ff359d5a9..87d641b309 100644 --- a/src/core/transform.js +++ b/src/core/transform.js @@ -4,7 +4,7 @@ * @for p5 */ -function transform(p5, fn){ +function transform(p5, fn) { /** * Applies a transformation matrix to the coordinate system. * @@ -211,7 +211,7 @@ function transform(p5, fn){ * @param {Number} p an element of the transformation matrix. * @chainable */ - fn.applyMatrix = function(...args) { + fn.applyMatrix = function (...args) { let isTypedArray = args[0] instanceof Object.getPrototypeOf(Uint8Array); if (Array.isArray(args[0]) || isTypedArray) { this._renderer.applyMatrix(...args[0]); @@ -255,7 +255,7 @@ function transform(p5, fn){ * circle(25, 25, 20); * } */ - fn.resetMatrix = function() { + fn.resetMatrix = function () { this._renderer.resetMatrix(); return this; }; @@ -416,7 +416,7 @@ function transform(p5, fn){ * box(); * } */ - fn.rotate = function(angle, axis) { + fn.rotate = function (angle, axis) { // p5._validateParameters('rotate', arguments); return this._renderer.rotate(this._toRadians(angle), axis); }; @@ -537,7 +537,7 @@ function transform(p5, fn){ * box(); * } */ - fn.rotateX = function(angle) { + fn.rotateX = function (angle) { this._assert3d('rotateX'); // p5._validateParameters('rotateX', arguments); return this._renderer.rotateX(this._toRadians(angle)); @@ -659,7 +659,7 @@ function transform(p5, fn){ * box(); * } */ - fn.rotateY = function(angle) { + fn.rotateY = function (angle) { this._assert3d('rotateY'); // p5._validateParameters('rotateY', arguments); return this._renderer.rotateY(this._toRadians(angle)); @@ -781,7 +781,7 @@ function transform(p5, fn){ * box(); * } */ - fn.rotateZ = function(angle) { + fn.rotateZ = function (angle) { this._assert3d('rotateZ'); // p5._validateParameters('rotateZ', arguments); return this._renderer.rotateZ(this._toRadians(angle)); @@ -940,7 +940,7 @@ function transform(p5, fn){ * @param {p5.Vector|Number[]} scales vector whose components should be used to scale. * @chainable */ - fn.scale = function(x, y, z) { + fn.scale = function (x, y, z) { // p5._validateParameters('scale', arguments); // Only check for Vector argument type if Vector is available if (x instanceof p5.Vector) { @@ -1026,7 +1026,7 @@ function transform(p5, fn){ * square(0, 0, 50); * } */ - fn.shearX = function(angle) { + fn.shearX = function (angle) { // p5._validateParameters('shearX', arguments); const rad = this._toRadians(angle); this._renderer.applyMatrix(1, 0, Math.tan(rad), 1, 0, 0); @@ -1096,7 +1096,7 @@ function transform(p5, fn){ * square(0, 0, 50); * } */ - fn.shearY = function(angle) { + fn.shearY = function (angle) { // p5._validateParameters('shearY', arguments); const rad = this._toRadians(angle); this._renderer.applyMatrix(1, Math.tan(rad), 0, 1, 0, 0); @@ -1263,7 +1263,7 @@ function transform(p5, fn){ * @param {p5.Vector} vector vector by which to translate. * @chainable */ - fn.translate = function(x, y, z) { + fn.translate = function (x, y, z) { // p5._validateParameters('translate', arguments); if (this._renderer.isP3D) { return this._renderer.translate(x, y, z); @@ -1537,7 +1537,7 @@ function transform(p5, fn){ * pop(); * } */ - fn.push = function() { + fn.push = function () { this._renderer.push(); }; @@ -1806,13 +1806,13 @@ function transform(p5, fn){ * pop(); * } */ - fn.pop = function() { + fn.pop = function () { this._renderer.pop(); }; } export default transform; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { transform(p5, p5.prototype); } diff --git a/src/data/index.js b/src/data/index.js index 6229d2ebd1..931dd052d3 100644 --- a/src/data/index.js +++ b/src/data/index.js @@ -1,5 +1,5 @@ import storage from './local_storage.js'; -export default function(p5){ +export default function (p5) { p5.registerAddon(storage); } diff --git a/src/data/local_storage.js b/src/data/local_storage.js index 505b500d61..d5f922226e 100644 --- a/src/data/local_storage.js +++ b/src/data/local_storage.js @@ -5,7 +5,7 @@ * This module defines the p5 methods for working with local storage */ -function storage(p5, fn){ +function storage(p5, fn) { /** * Stores a value in the web browser's local storage. * @@ -111,7 +111,7 @@ function storage(p5, fn){ * circle(50, 50, 30); * } */ - fn.storeItem = function(key, value) { + fn.storeItem = function (key, value) { if (typeof key !== 'string') { p5._friendlyError( `The argument that you passed to storeItem() - ${key} is not a string.`, @@ -126,7 +126,10 @@ function storage(p5, fn){ } if (typeof value === 'undefined') { - p5._friendlyError('You cannot store undefined variables using storeItem().', 'storeItem'); + p5._friendlyError( + 'You cannot store undefined variables using storeItem().', + 'storeItem' + ); } let type = typeof value; switch (type) { @@ -260,7 +263,7 @@ function storage(p5, fn){ * circle(50, 50, 30); * } */ - fn.getItem = function(key) { + fn.getItem = function (key) { let value = localStorage.getItem(key); const type = localStorage.getItem(`${key}p5TypeID`); if (typeof type === 'undefined') { @@ -418,7 +421,7 @@ function storage(p5, fn){ * removeItem('score'); * } */ - fn.removeItem = function(key) { + fn.removeItem = function (key) { if (typeof key !== 'string') { p5._friendlyError( `The argument that you passed to removeItem() - ${key} is not a string.`, @@ -432,6 +435,6 @@ function storage(p5, fn){ export default storage; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { storage(p5, p5.prototype); } diff --git a/src/dom/dom.js b/src/dom/dom.js index e975740ca0..82276febbc 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -19,7 +19,7 @@ import { Element } from './p5.Element'; import { MediaElement } from './p5.MediaElement'; import { File } from './p5.File'; -function dom(p5, fn){ +function dom(p5, fn) { /** * Searches the page for the first element that matches the given * CSS selector string. @@ -362,9 +362,7 @@ function dom(p5, fn){ function addElement(elt, pInst, media) { const node = pInst._userNode ? pInst._userNode : document.body; node.appendChild(elt); - const c = media - ? new MediaElement(elt, pInst) - : new Element(elt, pInst); + const c = media ? new MediaElement(elt, pInst) : new Element(elt, pInst); pInst._elements.push(c); return c; } @@ -1403,10 +1401,8 @@ function dom(p5, fn){ const arg0 = args[0]; if ( arg0 instanceof Element && - ( - arg0.elt instanceof HTMLDivElement || - arg0.elt instanceof HTMLSpanElement - ) + (arg0.elt instanceof HTMLDivElement || + arg0.elt instanceof HTMLSpanElement) ) { // If given argument is p5.Element of div/span type self = arg0; @@ -1848,6 +1844,6 @@ function dom(p5, fn){ export default dom; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { dom(p5, p5.prototype); } diff --git a/src/dom/index.js b/src/dom/index.js index 2bb5771c1e..18e320313c 100644 --- a/src/dom/index.js +++ b/src/dom/index.js @@ -3,7 +3,7 @@ import element from './p5.Element'; import media from './p5.MediaElement'; import file from './p5.File'; -export default function(p5){ +export default function (p5) { p5.registerAddon(dom); p5.registerAddon(element); p5.registerAddon(media); diff --git a/src/dom/p5.Element.js b/src/dom/p5.Element.js index 08dcc6260a..9db0c14def 100644 --- a/src/dom/p5.Element.js +++ b/src/dom/p5.Element.js @@ -48,7 +48,7 @@ class Element { // stop all audios/videos and detach all devices like microphone/camera etc // used as input/output for audios/videos. // if (this instanceof p5.MediaElement) { - if(this.stop){ + if (this.stop) { this.stop(); const sources = this.elt.srcObject; if (sources !== null) { @@ -72,10 +72,11 @@ class Element { // Jump one level up to the real p5 sketch stored in sketch._pInst. if (sketch && !sketch._elements && sketch._pInst) { - sketch = sketch._pInst; // climb one level up + sketch = sketch._pInst; // climb one level up } - if (sketch && sketch._elements) { // only if the array exists + if (sketch && sketch._elements) { + // only if the array exists const i = sketch._elements.indexOf(this); if (i !== -1) sketch._elements.splice(i, 1); } @@ -945,9 +946,9 @@ class Element { const AUTO = constants.AUTO; if (aW !== AUTO || aH !== AUTO) { if (aW === AUTO) { - aW = h * this.width / this.height; + aW = (h * this.width) / this.height; } else if (aH === AUTO) { - aH = w * this.height / this.width; + aH = (w * this.height) / this.width; } // set diff for cnv vs normal div if (this.elt instanceof HTMLCanvasElement) { @@ -1135,13 +1136,7 @@ class Element { 'translate(' + args[0] + 'px, ' + args[1] + 'px)'; } else if (args.length > 2) { this.elt.style.transform = - 'translate3d(' + - args[0] + - 'px,' + - args[1] + - 'px,' + - args[2] + - 'px)'; + 'translate3d(' + args[0] + 'px,' + args[1] + 'px,' + args[2] + 'px)'; if (args.length === 3) { this.elt.parentElement.style.perspective = '1000px'; } else { @@ -2274,9 +2269,9 @@ class Element { ctx.elt.removeEventListener(ev, f, false); ctx._events[ev] = null; } -}; +} -function element(p5, fn){ +function element(p5, fn) { /** * A class to describe an * HTML element. @@ -2368,6 +2363,6 @@ function element(p5, fn){ export default element; export { Element }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { element(p5, p5.prototype); } diff --git a/src/dom/p5.File.js b/src/dom/p5.File.js index 59b5f7e88b..eed9bdcc90 100644 --- a/src/dom/p5.File.js +++ b/src/dom/p5.File.js @@ -22,7 +22,6 @@ class File { this.data = undefined; } - static _createLoader(theFile, callback) { const reader = new FileReader(); reader.onload = function (e) { @@ -58,7 +57,7 @@ class File { } } -function file(p5, fn){ +function file(p5, fn) { /** * A class to describe a file. * @@ -352,6 +351,6 @@ function file(p5, fn){ export default file; export { File }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { file(p5, p5.prototype); } diff --git a/src/dom/p5.MediaElement.js b/src/dom/p5.MediaElement.js index 744620a5ae..019fcbf8e3 100644 --- a/src/dom/p5.MediaElement.js +++ b/src/dom/p5.MediaElement.js @@ -55,7 +55,8 @@ class MediaElement extends Element { Object.defineProperty(self, 'src', { get() { const firstChildSrc = self.elt.children[0].src; - const srcVal = self.elt.src === window.location.href ? '' : self.elt.src; + const srcVal = + self.elt.src === window.location.href ? '' : self.elt.src; const ret = firstChildSrc === window.location.href ? srcVal : firstChildSrc; return ret; @@ -73,13 +74,12 @@ class MediaElement extends Element { }); // private _onended callback, set by the method: onended(callback) - self._onended = function () { }; + self._onended = function () {}; self.elt.onended = function () { self._onended(self); }; } - /** * Plays audio or video from a media element. * @@ -439,8 +439,8 @@ class MediaElement extends Element { // if we turned on autoplay if (val && !oldVal) { // bind method to this scope - const setupAutoplayFailDetection = - () => this._setupAutoplayFailDetection(); + const setupAutoplayFailDetection = () => + this._setupAutoplayFailDetection(); // if media is ready to play, schedule check now if (this.elt.readyState === 4) { setupAutoplayFailDetection(); @@ -744,7 +744,11 @@ class MediaElement extends Element { } this.drawingContext.clearRect( - 0, 0, this.canvas.width, this.canvas.height); + 0, + 0, + this.canvas.width, + this.canvas.height + ); if (this.flipped === true) { this.drawingContext.save(); @@ -1261,16 +1265,14 @@ class MediaElement extends Element { // Cue inspired by JavaScript setTimeout, and the // Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org -function media(p5, fn){ +function media(p5, fn) { /** * Helpers for create methods. */ function addElement(elt, pInst, media) { const node = pInst._userNode ? pInst._userNode : document.body; node.appendChild(elt); - const c = media - ? new MediaElement(elt, pInst) - : new Element(elt, pInst); + const c = media ? new MediaElement(elt, pInst) : new Element(elt, pInst); pInst._elements.push(c); return c; } @@ -1618,8 +1620,7 @@ function media(p5, fn){ delete arg.flipped; } constraints = Object.assign({}, constraints, arg); - } - else if (typeof arg === 'function') { + } else if (typeof arg === 'function') { callback = arg; } } @@ -1629,25 +1630,27 @@ function media(p5, fn){ const domElement = document.createElement(VIDEO); // required to work in iOS 11 & up: domElement.setAttribute('playsinline', ''); - navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { - try { - if ('srcObject' in domElement) { - domElement.srcObject = stream; - } else { - domElement.src = window.URL.createObjectURL(stream); + navigator.mediaDevices + .getUserMedia(constraints) + .then(function (stream) { + try { + if ('srcObject' in domElement) { + domElement.srcObject = stream; + } else { + domElement.src = window.URL.createObjectURL(stream); + } + } catch (err) { + domElement.src = stream; } - } - catch (err) { - domElement.src = stream; - } - }).catch(e => { - if (e.name === 'NotFoundError') - p5._friendlyError('No webcam found on this device', 'createCapture'); - if (e.name === 'NotAllowedError') - p5._friendlyError('Access to the camera was denied', 'createCapture'); + }) + .catch(e => { + if (e.name === 'NotFoundError') + p5._friendlyError('No webcam found on this device', 'createCapture'); + if (e.name === 'NotAllowedError') + p5._friendlyError('Access to the camera was denied', 'createCapture'); - console.error(e); - }); + console.error(e); + }); const videoEl = addElement(domElement, this, true); videoEl.loadedmetadata = false; @@ -1712,10 +1715,10 @@ function media(p5, fn){ // Patch MediaElement to give it access to fn, which p5.sound may attach things to // if present in a sketch - MediaElement.prototype._getSoundOut = function() { + MediaElement.prototype._getSoundOut = function () { return p5.soundOut; }; - MediaElement.prototype._getAudioContext = function() { + MediaElement.prototype._getAudioContext = function () { if (typeof fn.getAudioContext === 'function') { return fn.getAudioContext(); } else { @@ -1753,6 +1756,6 @@ function media(p5, fn){ export default media; export { MediaElement }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { media(p5, p5.prototype); } diff --git a/src/events/acceleration.js b/src/events/acceleration.js index 5370c60ed6..991a619320 100644 --- a/src/events/acceleration.js +++ b/src/events/acceleration.js @@ -5,14 +5,11 @@ * @main Events */ -function acceleration(p5, fn, lifecycles){ - lifecycles.presetup = function(){ - const events = [ - 'deviceorientation', - 'devicemotion' - ]; - - for(const event of events){ +function acceleration(p5, fn, lifecycles) { + lifecycles.presetup = function () { + const events = ['deviceorientation', 'devicemotion']; + + for (const event of events) { window.addEventListener(event, this[`_on${event}`].bind(this), { passive: false, signal: this._removeSignal @@ -20,8 +17,11 @@ function acceleration(p5, fn, lifecycles){ } // Initialize device orientation value - this.deviceOrientation = typeof window !== 'undefined' && - window.innerWidth / window.innerHeight > 1.0 ? 'landscape' : 'portrait'; + this.deviceOrientation = + typeof window !== 'undefined' && + window.innerWidth / window.innerHeight > 1.0 + ? 'landscape' + : 'portrait'; }; /** @@ -666,15 +666,9 @@ function acceleration(p5, fn, lifecycles){ // so no conversion is needed. const rotZ = this._toDegrees(this.rotationZ); const pRotZ = this._toDegrees(this.pRotationZ); - if ( - (rotZ - pRotZ > 0 && rotZ - pRotZ < 270) || - rotZ - pRotZ < -270 - ) { + if ((rotZ - pRotZ > 0 && rotZ - pRotZ < 270) || rotZ - pRotZ < -270) { rotateDirectionZ = 'clockwise'; - } else if ( - rotZ - pRotZ < 0 || - rotZ - pRotZ > 270 - ) { + } else if (rotZ - pRotZ < 0 || rotZ - pRotZ > 270) { rotateDirectionZ = 'counter-clockwise'; } if (rotateDirectionZ !== this.pRotateDirectionZ) { @@ -712,6 +706,6 @@ function acceleration(p5, fn, lifecycles){ export default acceleration; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { acceleration(p5, p5.prototype); } diff --git a/src/events/index.js b/src/events/index.js index 447f3d0d10..f38c75a126 100644 --- a/src/events/index.js +++ b/src/events/index.js @@ -2,7 +2,7 @@ import acceleration from './acceleration.js'; import keyboard from './keyboard.js'; import pointer from './pointer.js'; -export default function(p5){ +export default function (p5) { p5.registerAddon(acceleration); p5.registerAddon(keyboard); p5.registerAddon(pointer); diff --git a/src/events/keyboard.js b/src/events/keyboard.js index 0e51b0632c..001fe91ec6 100644 --- a/src/events/keyboard.js +++ b/src/events/keyboard.js @@ -4,12 +4,7 @@ * @for p5 */ export function isCode(input) { - const leftRightKeys = [ - 'Alt', - 'Shift', - 'Control', - 'Meta' - ]; + const leftRightKeys = ['Alt', 'Shift', 'Control', 'Meta']; if (leftRightKeys.includes(input)) { return false; } @@ -19,16 +14,11 @@ export function isCode(input) { return input.length > 1; } -function keyboard(p5, fn, lifecycles){ - lifecycles.presetup = function(){ - const events = [ - 'keydown', - 'keyup', - 'keypress', - 'blur' - ]; +function keyboard(p5, fn, lifecycles) { + lifecycles.presetup = function () { + const events = ['keydown', 'keyup', 'keypress', 'blur']; - for(const event of events){ + for (const event of events) { window.addEventListener(event, this[`_on${event}`].bind(this), { passive: false, signal: this._removeSignal @@ -613,7 +603,7 @@ function keyboard(p5, fn, lifecycles){ * // return false; * } */ - fn._onkeydown = function(e) { + fn._onkeydown = function (e) { if (this._downKeys[e.code]) { return; } @@ -779,7 +769,7 @@ function keyboard(p5, fn, lifecycles){ * // return false; * } */ - fn._onkeyup = function(e) { + fn._onkeyup = function (e) { if (typeof this._customActions.keyReleased === 'function') { const executeDefault = this._customActions.keyReleased(e); if (executeDefault === false) { @@ -790,7 +780,6 @@ function keyboard(p5, fn, lifecycles){ delete this._downKeyCodes[e.code]; delete this._downKeys[e.key]; - if (!this._areDownKeys()) { this.keyIsPressed = false; this.key = ''; @@ -802,7 +791,6 @@ function keyboard(p5, fn, lifecycles){ const lastPressedKey = Object.keys(this._downKeys).pop(); this.key = lastPressedKey; } - }; /** @@ -928,7 +916,7 @@ function keyboard(p5, fn, lifecycles){ * // return false; * } */ - fn._onkeypress = function(e) { + fn._onkeypress = function (e) { if (e.which === this._lastKeyCodeTyped && e.repeat) { // prevent multiple firings return; @@ -950,7 +938,7 @@ function keyboard(p5, fn, lifecycles){ * not focused on the element we must assume all keys currently down have * been released. */ - fn._onblur = function(e) { + fn._onblur = function (e) { this._downKeys = {}; }; @@ -1064,7 +1052,7 @@ function keyboard(p5, fn, lifecycles){ * circle(x, y, 5); * } */ - fn.keyIsDown = function(input) { + fn.keyIsDown = function (input) { if (isCode(input)) { return this._downKeyCodes[input] || this._downKeys[input] || false; } else { @@ -1081,7 +1069,7 @@ function keyboard(p5, fn, lifecycles){ * keyIsPressed property to true. * @private */ - fn._areDownKeys = function() { + fn._areDownKeys = function () { for (const key in this._downKeys) { if (this._downKeys.hasOwnProperty(key) && this._downKeys[key] === true) { return true; @@ -1093,6 +1081,6 @@ function keyboard(p5, fn, lifecycles){ export default keyboard; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { keyboard(p5, p5.prototype); } diff --git a/src/events/pointer.js b/src/events/pointer.js index 2300fac536..cfdcea2eb2 100644 --- a/src/events/pointer.js +++ b/src/events/pointer.js @@ -4,8 +4,8 @@ * @for p5 */ -function pointer(p5, fn, lifecycles){ - lifecycles.presetup = function(){ +function pointer(p5, fn, lifecycles) { + lifecycles.presetup = function () { const events = [ 'pointerdown', 'pointerup', @@ -17,16 +17,20 @@ function pointer(p5, fn, lifecycles){ 'dblclick', 'wheel' ]; - for(const event of events){ + for (const event of events) { window.addEventListener(event, this[`_on${event}`].bind(this), { passive: false, signal: this._removeSignal }); } - window.addEventListener('blur', () => { - this.mouseIsPressed = false; - }, { signal: this._removeSignal }); + window.addEventListener( + 'blur', + () => { + this.mouseIsPressed = false; + }, + { signal: this._removeSignal } + ); }; /** @@ -853,7 +857,7 @@ function pointer(p5, fn, lifecycles){ } }; - fn._updateMouseCoords = function() { + fn._updateMouseCoords = function () { this.pmouseX = this.mouseX; this.pmouseY = this.mouseY; this.pwinMouseX = this.winMouseX; @@ -882,20 +886,17 @@ function pointer(p5, fn, lifecycles){ }; } - fn._setMouseButton = function(e) { + fn._setMouseButton = function (e) { // Check all active touches to determine button states - this.mouseButton.left = Array.from(this._activePointers.values()) - .some(touch => - (touch.buttons & 1) !== 0 - ); - this.mouseButton.center = Array.from(this._activePointers.values()) - .some(touch => - (touch.buttons & 4) !== 0 - ); - this.mouseButton.right = Array.from(this._activePointers.values()) - .some(touch => - (touch.buttons & 2) !== 0 - ); + this.mouseButton.left = Array.from(this._activePointers.values()).some( + touch => (touch.buttons & 1) !== 0 + ); + this.mouseButton.center = Array.from(this._activePointers.values()).some( + touch => (touch.buttons & 4) !== 0 + ); + this.mouseButton.right = Array.from(this._activePointers.values()).some( + touch => (touch.buttons & 2) !== 0 + ); }; /** @@ -1063,7 +1064,7 @@ function pointer(p5, fn, lifecycles){ * // return false; * } */ - fn._onpointermove = function(e) { + fn._onpointermove = function (e) { let executeDefault; this._updatePointerCoords(e); this._activePointers.set(e.pointerId, e); @@ -1227,7 +1228,7 @@ function pointer(p5, fn, lifecycles){ * strokeWeight(10); * } */ - fn._onpointerdown = function(e) { + fn._onpointerdown = function (e) { let executeDefault; this.mouseIsPressed = true; @@ -1379,7 +1380,7 @@ function pointer(p5, fn, lifecycles){ * strokeWeight(10); * } */ - fn._onpointerup = function(e) { + fn._onpointerup = function (e) { let executeDefault; this.mouseIsPressed = false; @@ -1399,7 +1400,7 @@ function pointer(p5, fn, lifecycles){ fn._ondragend = fn._onpointerup; fn._ondragover = fn._onpointermove; - fn._onpointercancel = function(e) { + fn._onpointercancel = function (e) { this._activePointers.delete(e.pointerId); this._setMouseButton(e); this._updatePointerCoords(e); @@ -1545,7 +1546,7 @@ function pointer(p5, fn, lifecycles){ * strokeWeight(10); * } */ - fn._onclick = function(e) { + fn._onclick = function (e) { if (typeof this._customActions.mouseClicked === 'function') { const executeDefault = this._customActions.mouseClicked(e); if (executeDefault === false) { @@ -1668,7 +1669,7 @@ function pointer(p5, fn, lifecycles){ * } */ - fn._ondblclick = function(e) { + fn._ondblclick = function (e) { if (typeof this._customActions.doubleClicked === 'function') { const executeDefault = this._customActions.doubleClicked(e); if (executeDefault === false) { @@ -1808,7 +1809,7 @@ function pointer(p5, fn, lifecycles){ * // return false; * } */ - fn._onwheel = function(e) { + fn._onwheel = function (e) { this._mouseWheelDeltaY = e.deltaY; if (typeof this._customActions.mouseWheel === 'function') { e.delta = e.deltaY; @@ -1868,13 +1869,16 @@ function pointer(p5, fn, lifecycles){ * requestPointerLock(); * } */ - fn.requestPointerLock = function() { + fn.requestPointerLock = function () { // pointer lock object forking for cross browser const canvas = this._curElement.elt; canvas.requestPointerLock = canvas.requestPointerLock || canvas.mozRequestPointerLock; if (!canvas.requestPointerLock) { - p5._friendlyError('requestPointerLock is not implemented in this browser', 'requestPointerLock'); + p5._friendlyError( + 'requestPointerLock is not implemented in this browser', + 'requestPointerLock' + ); return false; } canvas.requestPointerLock(); @@ -1933,13 +1937,13 @@ function pointer(p5, fn, lifecycles){ * } * } */ - fn.exitPointerLock = function() { + fn.exitPointerLock = function () { document.exitPointerLock(); }; } export default pointer; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { pointer(p5, p5.prototype); } diff --git a/src/friendly_errors/fes.js b/src/friendly_errors/fes.js index 73865d4c75..4b25bf81a2 100644 --- a/src/friendly_errors/fes.js +++ b/src/friendly_errors/fes.js @@ -2,15 +2,21 @@ import { TL } from 'tl-util'; TL.addTranslations(['en', 'en-US', 'en-GB'], { paramTooFew: { - '${minArgs}_[one]': 'Expected at least ${minArgs} argument, but received fewer in ${functionName}(). ${referenceLink}', - '${minArgs}_[*]': 'Expected at least ${minArgs} arguments, but received fewer in ${functionName}(). ${referenceLink}' + '${minArgs}_[one]': + 'Expected at least ${minArgs} argument, but received fewer in ${functionName}(). ${referenceLink}', + '${minArgs}_[*]': + 'Expected at least ${minArgs} arguments, but received fewer in ${functionName}(). ${referenceLink}' }, paramTooMany: { - '${minArgs}_[one]': 'Expected at most ${minArgs} argument, but received fewer in ${functionName}(). ${referenceLink}', - '${minArgs}_[*]': 'Expected at most ${minArgs} arguments, but received fewer in ${functionName}(). ${referenceLink}' + '${minArgs}_[one]': + 'Expected at most ${minArgs} argument, but received fewer in ${functionName}(). ${referenceLink}', + '${minArgs}_[*]': + 'Expected at most ${minArgs} arguments, but received fewer in ${functionName}(). ${referenceLink}' }, - paramType: 'Expected ${expectedType} at the ${position} parameter in ${functionName}.', - redeclare: '${errorType} "${name}" on line ${line} is being redeclared and conflicts with a p5.js ${errorType}. p5.js reference: ${url}', + paramType: + 'Expected ${expectedType} at the ${position} parameter in ${functionName}.', + redeclare: + '${errorType} "${name}" on line ${line} is being redeclared and conflicts with a p5.js ${errorType}. p5.js reference: ${url}', referenceLink: 'For more information, see ${referenceURL}.', ordinalFirst: 'first', typeString: 'string', @@ -25,7 +31,7 @@ let translationPromise; if (localTranslation) { TL.addTranslations(defaultLanguage, JSON.parse(localTranslation)); translationPromise = Promise.resolve(); -}else{ +} else { translationPromise = fetch('./fes-zh.json') .then(res => { if (res.ok) return res.json(); @@ -49,9 +55,10 @@ export class FES { static #printMessage(method, strings, ...values) { if (FES.disableFriendlyErrors) return; const styleStrings = []; - const translation = TL.tl(strings, ...values.map( - value => { - if(value instanceof StyledMessage){ + const translation = TL.tl( + strings, + ...values.map(value => { + if (value instanceof StyledMessage) { const ret = `%c${value.message.toString(FES.languageCode)}%c`; styleStrings.push(value.styleString, ''); return ret; @@ -61,14 +68,17 @@ export class FES { } else { return value; } - } - )); + }) + ); const results = translation.toString(FES.languageCode); const executor = options => { - const { prefix } = Object.assign({ - prefix: TL.tl`🌸 p5.js says: ` - }, options); + const { prefix } = Object.assign( + { + prefix: TL.tl`🌸 p5.js says: ` + }, + options + ); let footer = ''; if (options?.reference) { @@ -121,15 +131,13 @@ export class FES { static TL = TL; static premade = { - ordinals: [ - TL.tl`first` - ], + ordinals: [TL.tl`first`], types: { string: TL.tl`string`, boolean: TL.tl`boolean`, number: TL.tl`number` } - } + }; } export class StyledMessage { @@ -147,11 +155,10 @@ export class StyledMessage { } export function style(message, mod) { - const styleString = Object.entries(mod) - .reduce((acc, [key, val]) => { - acc += `${key}: ${val};`; - return acc; - }, ''); + const styleString = Object.entries(mod).reduce((acc, [key, val]) => { + acc += `${key}: ${val};`; + return acc; + }, ''); if (message instanceof StyledMessage) { message.styleString += styleString; return message; @@ -181,16 +188,16 @@ export function underline(message) { } /** - * Takes a message and a p5 function func, and adds a link pointing to - * the reference documentation of func at the end of the message - * - * @method mapToReference - * @private - * @param {String} message the words to be said - * @param {String} [func] the name of function - * - * @returns {String} - */ + * Takes a message and a p5 function func, and adds a link pointing to + * the reference documentation of func at the end of the message + * + * @method mapToReference + * @private + * @param {String} message the words to be said + * @param {String} [func] the name of function + * + * @returns {String} + */ function mapToReference(message, func) { let msgWithReference = ''; if (func == null || func.substring(0, 4) === 'load') { @@ -204,14 +211,14 @@ function mapToReference(message, func) { methodParts.length === 1 ? func : methodParts.slice(2).join('/'); //Whenever func having p5.[Class] is encountered, we need to have the error link as mentioned below else different link - if(funcName.startsWith('p5.')){ + if (funcName.startsWith('p5.')) { msgWithReference = ` (https://p5js.org/reference/${referenceSection}.${funcName})`; - }else{ + } else { msgWithReference = ` (https://p5js.org/reference/${referenceSection}/${funcName})`; } } return msgWithReference; -}; +} // Re-export TL export { TL }; diff --git a/src/friendly_errors/fes_core.js b/src/friendly_errors/fes_core.js index e709f68421..82e6df2f09 100644 --- a/src/friendly_errors/fes_core.js +++ b/src/friendly_errors/fes_core.js @@ -23,10 +23,15 @@ * https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md */ import { errorTable, entryPoints } from './browser_errors'; -import { errorStackParser, processStack, printFriendlyStack, getFriendlyStack } from './stacktrace'; +import { + errorStackParser, + processStack, + printFriendlyStack, + getFriendlyStack +} from './stacktrace'; import { FES } from './fes'; -function fesCore(p5, fn, lifecycles){ +function fesCore(p5, fn, lifecycles) { // This is a lazily-defined list of p5 symbols that may be // misused by beginners at top-level code, outside of setup/draw. We'd like // to detect these errors and help the user by suggesting they move them @@ -83,9 +88,7 @@ function fesCore(p5, fn, lifecycles){ const EDIT_DIST_THRESHOLD = 2; if (typeof IS_MINIFIED !== 'undefined') { - p5._friendlyError = - p5._checkForUserDefinedFunctions = - () => {}; + p5._friendlyError = p5._checkForUserDefinedFunctions = () => {}; } else { /** * Takes a message and a p5 function func, and adds a link pointing to @@ -111,9 +114,9 @@ function fesCore(p5, fn, lifecycles){ methodParts.length === 1 ? func : methodParts.slice(2).join('/'); //Whenever func having p5.[Class] is encountered, we need to have the error link as mentioned below else different link - if(funcName.startsWith('p5.')){ + if (funcName.startsWith('p5.')) { msgWithReference = FES.log`${message} (https://p5js.org/reference/${referenceSection}.${funcName})`; - }else{ + } else { msgWithReference = FES.log`${message} (https://p5js.org/reference/${referenceSection}/${funcName})`; } } @@ -146,7 +149,7 @@ function fesCore(p5, fn, lifecycles){ * @param {String} message Message to be printed * @param {String} [func] Name of the function linked to error */ - p5._friendlyError = function(message, func) { + p5._friendlyError = function (message, func) { if (p5.disableFriendlyErrors) return; p5._report(message, func); }; @@ -160,7 +163,7 @@ function fesCore(p5, fn, lifecycles){ * * @private */ - p5.isPreloadSupported = function() { + p5.isPreloadSupported = function () { return false; }; @@ -207,9 +210,11 @@ function fesCore(p5, fn, lifecycles){ !context[fxns[lowercase]] && typeof context[prop] === 'function' ) { - FES.log`It seems that you may have accidentally written ${prop} instead of ${fxns[lowercase]}. Please correct it if it's not intentional.`({ - reference: fxns[lowercase] - }); + FES.log`It seems that you may have accidentally written ${prop} instead of ${fxns[lowercase]}. Please correct it if it's not intentional.`( + { + reference: fxns[lowercase] + } + ); } } }; @@ -290,8 +295,8 @@ function fesCore(p5, fn, lifecycles){ // this is already done in the suggestions variable, one link for each // suggestion. msg({ - reference: matchedSymbols.length === 1 ? - matchedSymbols[0].name : undefined + reference: + matchedSymbols.length === 1 ? matchedSymbols[0].name : undefined }); return true; } @@ -385,10 +390,7 @@ function fesCore(p5, fn, lifecycles){ let stacktrace = errorStackParser.parse(error); // process the stacktrace from the browser and simplify it to give // friendlyStack. - let [isInternal, friendlyStack] = processStack( - error, - stacktrace - ); + let [isInternal, friendlyStack] = processStack(error, stacktrace); // if this is an internal library error, the type of the error is not relevant, // only the user code that lead to it is. @@ -501,9 +503,10 @@ function fesCore(p5, fn, lifecycles){ if (friendlyStack) { const msg = getFriendlyStack(friendlyStack); - if(msg) FES.log`${msg}`({ - prefix: false - }); + if (msg) + FES.log`${msg}`({ + prefix: false + }); } break; } @@ -657,4 +660,4 @@ function computeEditDistance(w1, w2) { } return cur[l2]; -}; +} diff --git a/src/friendly_errors/param_validator.js b/src/friendly_errors/param_validator.js index 6e01e9a699..0f8a08c39f 100644 --- a/src/friendly_errors/param_validator.js +++ b/src/friendly_errors/param_validator.js @@ -5,7 +5,11 @@ import * as constants from '../core/constants.js'; import * as z from 'zod/mini'; import dataDoc from '../../docs/parameterData.json'; import { FES } from './fes.js'; -import { errorStackParser, processStack, getFriendlyStack } from './stacktrace.js'; +import { + errorStackParser, + processStack, + getFriendlyStack +} from './stacktrace.js'; z.config(z.locales.en()); let documentationData = dataDoc; @@ -63,14 +67,14 @@ function validateParams(p5, fn, lifecycles) { // Start initializing `schemaMap` with primitive types. `schemaMap` will // eventually contain both primitive types and web API objects. const schemaMap = { - 'Any': z.any(), - 'Array': z.array(z.any()), - 'Boolean': z.boolean(), - 'Function': z.function(), - 'Integer': z.number().check(z.int()), - 'Number': z.union([z.number(), z.literal(Infinity), z.literal(-Infinity)]), - 'Object': z.object({}), - 'String': z.string() + Any: z.any(), + Array: z.array(z.any()), + Boolean: z.boolean(), + Function: z.function(), + Integer: z.number().check(z.int()), + Number: z.union([z.number(), z.literal(Infinity), z.literal(-Infinity)]), + Object: z.object({}), + String: z.string() }; const webAPIObjects = [ @@ -100,7 +104,18 @@ function validateParams(p5, fn, lifecycles) { // For mapping 0-indexed parameters to their ordinal representation, e.g. // "first" for 0, "second" for 1, "third" for 2, etc. - const ordinals = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth']; + const ordinals = [ + 'first', + 'second', + 'third', + 'fourth', + 'fifth', + 'sixth', + 'seventh', + 'eighth', + 'ninth', + 'tenth' + ]; function extractFuncNameAndClass(func) { const ichDot = func.lastIndexOf('.'); @@ -149,7 +164,7 @@ function validateParams(p5, fn, lifecycles) { const { funcName, funcClass } = extractFuncNameAndClass(func); let funcInfo = documentationData[funcClass][funcName]; - if(!funcInfo) return; + if (!funcInfo) return; let overloads = []; if (funcInfo.hasOwnProperty('overloads')) { @@ -211,15 +226,20 @@ function validateParams(p5, fn, lifecycles) { // our constants sometimes have numeric or non-primitive values. // 2) In some cases, the type can be constants or strings, making z.enum() // insufficient for the use case. - else if (baseType.includes('|') && baseType.split('|').every(t => validBracketNesting(t))) { + else if ( + baseType.includes('|') && + baseType.split('|').every(t => validBracketNesting(t)) + ) { const types = baseType.split('|'); - typeSchema = z.union(types - .map(t => generateTypeSchema(t)) - .filter(s => s !== undefined)); + typeSchema = z.union( + types.map(t => generateTypeSchema(t)).filter(s => s !== undefined) + ); } else if (baseType.endsWith('[]')) { typeSchema = z.array(generateTypeSchema(baseType.slice(0, -2))); } else { - throw new Error(`Unsupported type '${baseType}' in parameter validation. Please report this issue.`); + throw new Error( + `Unsupported type '${baseType}' in parameter validation. Please report this issue.` + ); } return typeSchema; @@ -264,7 +284,9 @@ function validateParams(p5, fn, lifecycles) { return [params]; } - const requiredParamsCount = params.filter(p => p === null || !p.endsWith('?')).length; + const requiredParamsCount = params.filter( + p => p === null || !p.endsWith('?') + ).length; const result = []; for (let i = requiredParamsCount; i <= params.length; i++) { @@ -288,7 +310,10 @@ function validateParams(p5, fn, lifecycles) { rest = params.pop(); } - let combined = z.tuple(params.map(s => s.schema), rest?.schema); + let combined = z.tuple( + params.map(s => s.schema), + rest?.schema + ); // if (rest) { // combined = combined.rest(rest.schema); // } @@ -331,9 +356,9 @@ function validateParams(p5, fn, lifecycles) { const numArgs = args.length; const schemaItems = schema.def.items; const numSchemaItems = schemaItems.length; - const numRequiredSchemaItems = schemaItems - .filter(item => !item.safeParse(undefined).success) - .length; + const numRequiredSchemaItems = schemaItems.filter( + item => !item.safeParse(undefined).success + ).length; if (numArgs >= numRequiredSchemaItems && numArgs <= numSchemaItems) { score = 0; @@ -350,11 +375,12 @@ function validateParams(p5, fn, lifecycles) { // message will show that we're expecting at most 2 arguments, but more // are received. else { - score = Math.abs( - numArgs < numRequiredSchemaItems ? - numRequiredSchemaItems - numArgs : - numArgs - numSchemaItems - ) * 4; + score = + Math.abs( + numArgs < numRequiredSchemaItems + ? numRequiredSchemaItems - numArgs + : numArgs - numSchemaItems + ) * 4; } for (let i = 0; i < Math.min(schemaItems.length, args.length); i++) { @@ -405,12 +431,19 @@ function validateParams(p5, fn, lifecycles) { let currentError = zodErrorObj.issues[0]; // Helper function to build a type mismatch message. - const buildTypeMismatchMessage = - (actualType, expectedTypeStr, position) => { - const positionStr = position ? FES.log`at the ${ordinals[position]} parameter` : ''; - const actualTypeStr = actualType ? FES.log`, but received ${actualType}` : ''; - return FES.log`Expected ${expectedTypeStr} ${positionStr}${actualTypeStr}`; - }; + const buildTypeMismatchMessage = ( + actualType, + expectedTypeStr, + position + ) => { + const positionStr = position + ? FES.log`at the ${ordinals[position]} parameter` + : ''; + const actualTypeStr = actualType + ? FES.log`, but received ${actualType}` + : ''; + return FES.log`Expected ${expectedTypeStr} ${positionStr}${actualTypeStr}`; + }; // Union errors occur when a parameter can be of multiple types but is not // of any of them. In this case, aggregate all possible types and print @@ -419,7 +452,8 @@ function validateParams(p5, fn, lifecycles) { const processUnionError = error => { const expectedTypes = new Set(); - let actualType, message = ''; + let actualType, + message = ''; const collectIssue = issue => { if (!issue) return; @@ -441,7 +475,9 @@ function validateParams(p5, fn, lifecycles) { ) { expectedTypes.add('number'); } else { - expectedTypes.add('constant (please refer to documentation for allowed values)'); + expectedTypes.add( + 'constant (please refer to documentation for allowed values)' + ); actualType = args[error.path[0]]; } } else if (issue.code === 'custom') { @@ -460,7 +496,7 @@ function validateParams(p5, fn, lifecycles) { }); if (expectedTypes.size > 0) { - if (error.path?.length > 0 && args[error.path[0]] instanceof Promise) { + if (error.path?.length > 0 && args[error.path[0]] instanceof Promise) { message = FES.log`Did you mean to put \`await\` before a loading function? An unexpected Promise was found. `; } @@ -468,7 +504,9 @@ function validateParams(p5, fn, lifecycles) { const position = error.path.join('.'); message = FES.log`${message}${buildTypeMismatchMessage( - actualType, expectedTypesStr, position + actualType, + expectedTypesStr, + position )} in ${func + '()'}.`; } @@ -500,7 +538,7 @@ function validateParams(p5, fn, lifecycles) { const position = FES.premade.ordinals[currentError.path.join('.')]; const expectedType = FES.premade.types[currentError.expected]; // NOTE: need type info - message = FES.log`Expected ${expectedType} at the ${position} parameter in ${func + '()'}.` + message = FES.log`Expected ${expectedType} at the ${position} parameter in ${func + '()'}.`; break; } case 'too_big': { @@ -514,7 +552,7 @@ function validateParams(p5, fn, lifecycles) { const position = FES.premade.ordinals[currentError.path.join('.')]; const match = currentError.message.match(/Input not instance of (\w+)/); if (match) { - message = FES.log`Expected ${match[1]} at the ${position} parameter in ${func + '()'}.` + message = FES.log`Expected ${match[1]} at the ${position} parameter in ${func + '()'}.`; break; } } @@ -603,7 +641,7 @@ function validateParams(p5, fn, lifecycles) { // Suppress FES param checking for the duration of a callback. // Use this to wrap internal p5 calls that happen after an await. // NOTE: shares the same _isUserCall flag logic as the decorator below. - fn._internal = function(callback) { + fn._internal = function (callback) { const wasInternalCall = this._isUserCall; this._isUserCall = true; try { @@ -619,16 +657,20 @@ function validateParams(p5, fn, lifecycles) { ({ path }) => { return path.startsWith('p5.prototype'); }, - function(target, { kind, name }){ - if(kind === 'method'){ - return function(...args){ + function (target, { kind, name }) { + if (kind === 'method') { + return function (...args) { if (p5.disableFriendlyErrors) { return target.apply(this, args); } const wasInternalCall = this._isUserCall; this._isUserCall = true; try { - if (!wasInternalCall && !p5.disableFriendlyErrors && !p5.disableParameterValidator) { + if ( + !wasInternalCall && + !p5.disableFriendlyErrors && + !p5.disableParameterValidator + ) { validate(name, args); } return target.apply(this, args); @@ -640,17 +682,17 @@ function validateParams(p5, fn, lifecycles) { } ); - p5.extendParameterValidation = (data) => { + p5.extendParameterValidation = data => { documentationData = mergeDeep(documentationData, data); }; - lifecycles.presetup = function(){ + lifecycles.presetup = function () { loadP5Constructors(); }; } function isObject(item) { - return (item && typeof item === 'object' && !Array.isArray(item)); + return item && typeof item === 'object' && !Array.isArray(item); } // Deep merge implementation: https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge diff --git a/src/friendly_errors/sketch_verifier.js b/src/friendly_errors/sketch_verifier.js index a2efd37975..45a40608cd 100644 --- a/src/friendly_errors/sketch_verifier.js +++ b/src/friendly_errors/sketch_verifier.js @@ -25,7 +25,7 @@ const ignoreFunction = [ 'keyPressed', 'keyReleased', 'keyTyped', - 'windowResized', + 'windowResized' // 'name', // 'parent', // 'toString', @@ -82,15 +82,19 @@ export const verifierUtils = { const ast = parse(code, { ecmaVersion: 'latest', sourceType: 'module', - locations: true // This helps us get the line number. + locations: true // This helps us get the line number. }); walk(ast, { VariableDeclarator(node) { if (node.id.type === 'Identifier') { - const category = node.init && ['ArrowFunctionExpression', 'FunctionExpression'].includes(node.init.type) - ? 'functions' - : 'variables'; + const category = + node.init && + ['ArrowFunctionExpression', 'FunctionExpression'].includes( + node.init.type + ) + ? 'functions' + : 'variables'; userDefinitions[category].push({ name: node.id.name, line: node.loc.start.line + lineOffset @@ -162,7 +166,7 @@ export const verifierUtils = { for (let { name, line } of allDefinitions) { const libDefinition = constants[name]; if (libDefinition !== undefined) { - const message = generateFriendlyError('constant', name, line+1); + const message = generateFriendlyError('constant', name, line + 1); FES.log`${message}`(); return true; } @@ -173,13 +177,18 @@ export const verifierUtils = { // - It is a member of p5.prototype // - Its name does not start with `_` const globalFunctions = new Set( - Object.getOwnPropertyNames(p5.prototype) - .filter(key => !key.startsWith('_') && key !== 'constructor') + Object.getOwnPropertyNames(p5.prototype).filter( + key => !key.startsWith('_') && key !== 'constructor' + ) ); for (let { name, line } of allDefinitions) { if (!ignoreFunction.includes(name) && globalFunctions.has(name)) { - const message = generateFriendlyError(FES.log`function`, name, line+1); + const message = generateFriendlyError( + FES.log`function`, + name, + line + 1 + ); FES.log`${message}`(); return true; } @@ -212,15 +221,15 @@ export const verifierUtils = { */ runFES: async function (p5) { const userCode = await verifierUtils.getUserCode(); - const userDefinedVariablesAndFuncs = verifierUtils - .extractUserDefinedVariablesAndFuncs(userCode); + const userDefinedVariablesAndFuncs = + verifierUtils.extractUserDefinedVariablesAndFuncs(userCode); verifierUtils.checkForConstsAndFuncs(userDefinedVariablesAndFuncs, p5); } }; function sketchVerifier(p5, _fn, lifecycles) { - lifecycles.presetup = async function() { + lifecycles.presetup = async function () { if (!p5.disableFriendlyErrors && !p5.disableSketchChecker) { verifierUtils.runFES(p5); } diff --git a/src/friendly_errors/stacktrace.js b/src/friendly_errors/stacktrace.js index 6b2269e961..bf92d00831 100644 --- a/src/friendly_errors/stacktrace.js +++ b/src/friendly_errors/stacktrace.js @@ -63,11 +63,11 @@ function ErrorStackParser() { }, parseV8OrIE: function ErrorStackParser$$parseV8OrIE(error) { - let filtered = error.stack.split('\n').filter(function(line) { + let filtered = error.stack.split('\n').filter(function (line) { return !!line.match(CHROME_IE_STACK_REGEXP); }, this); - return filtered.map(function(line) { + return filtered.map(function (line) { if (line.indexOf('(eval ') > -1) { // Throw away eval information until we implement stacktrace.js/stackframe#8 line = line @@ -109,11 +109,11 @@ function ErrorStackParser() { }, parseFFOrSafari: function ErrorStackParser$$parseFFOrSafari(error) { - let filtered = error.stack.split('\n').filter(function(line) { + let filtered = error.stack.split('\n').filter(function (line) { return !line.match(SAFARI_NATIVE_CODE_REGEXP); }, this); - return filtered.map(function(line) { + return filtered.map(function (line) { // Throw away eval information until we implement stacktrace.js/stackframe#8 if (line.indexOf(' > eval') > -1) { line = line.replace( @@ -201,14 +201,14 @@ function ErrorStackParser() { // Opera 10.65+ Error.stack very similar to FF/Safari parseOpera11: function ErrorStackParser$$parseOpera11(error) { - let filtered = error.stack.split('\n').filter(function(line) { + let filtered = error.stack.split('\n').filter(function (line) { return ( !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && !line.match(/^Error created at/) ); }, this); - return filtered.map(function(line) { + return filtered.map(function (line) { let tokens = line.split('@'); let locationParts = this.extractLocation(tokens.pop()); let functionCall = tokens.shift() || ''; @@ -395,7 +395,7 @@ export function printFriendlyStack(friendlyStack) { prefix: false }); } -}; +} export function getFriendlyStack(stacktrace, compact = false) { if (compact) { diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js index bd9da38d9b..56422b4dea 100644 --- a/src/image/filterRenderer2D.js +++ b/src/image/filterRenderer2D.js @@ -7,7 +7,7 @@ import { populateGLSLHooks, setWebGLTextureParams, setWebGLUniformValue -} from "../webgl/utils"; +} from '../webgl/utils'; import * as constants from '../core/constants'; import { filterParamDefaults } from './const'; @@ -57,7 +57,7 @@ class FilterRenderer2D { webglVersion, states: { textureWrapX: constants.CLAMP, - textureWrapY: constants.CLAMP, + textureWrapY: constants.CLAMP }, _arraysEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b), _getEmptyTexture: () => { @@ -68,25 +68,29 @@ class FilterRenderer2D { } return this._emptyTexture; }, - _initShader: (shader) => { + _initShader: shader => { const gl = this.gl; const vertShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertShader, shader.vertSrc()); gl.compileShader(vertShader); if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) { - throw new Error(`Yikes! An error occurred compiling the vertex shader: ${ - gl.getShaderInfoLog(vertShader) - }`); + throw new Error( + `Yikes! An error occurred compiling the vertex shader: ${gl.getShaderInfoLog( + vertShader + )}` + ); } const fragShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragShader, shader.fragSrc()); gl.compileShader(fragShader); if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) { - throw new Error(`Darn! An error occurred compiling the fragment shader: ${ - gl.getShaderInfoLog(fragShader) - }`); + throw new Error( + `Darn! An error occurred compiling the fragment shader: ${gl.getShaderInfoLog( + fragShader + )}` + ); } const program = gl.createProgram(); @@ -104,7 +108,7 @@ class FilterRenderer2D { shader._vertShader = vertShader; shader._fragShader = fragShader; }, - getTexture: (input) => { + getTexture: input => { let src = input; if (src instanceof Framebuffer) { src = src.color; @@ -122,17 +126,17 @@ class FilterRenderer2D { populateHooks: (shader, src, shaderType) => { return populateGLSLHooks(shader, src, shaderType); }, - _getShaderAttributes: (shader) => { + _getShaderAttributes: shader => { return getWebGLShaderAttributes(shader, this.gl); }, - getUniformMetadata: (shader) => { + getUniformMetadata: shader => { return getWebGLUniformMetadata(shader, this.gl); }, _finalizeShader: () => {}, - _useShader: (shader) => { + _useShader: shader => { this.gl.useProgram(shader._glProgram); }, - bindTexture: (tex) => { + bindTexture: tex => { // bind texture using gl context + glTarget and // generated gl texture object this.gl.bindTexture(this.gl.TEXTURE_2D, tex.getTexture().texture); @@ -141,7 +145,7 @@ class FilterRenderer2D { // unbind per above, disable texturing on glTarget this.gl.bindTexture(this.gl.TEXTURE_2D, null); }, - _unbindFramebufferTexture: (uniform) => { + _unbindFramebufferTexture: uniform => { // Make sure an empty texture is bound to the slot so that we don't // accidentally leave a framebuffer bound, causing a feedback loop // when something else tries to write to it @@ -155,17 +159,35 @@ class FilterRenderer2D { const gl = this.gl; const tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, - gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + width, + height, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null + ); // TODO use format and data type - return { texture: tex, glFormat: gl.RGBA, glDataType: gl.UNSIGNED_BYTE }; + return { + texture: tex, + glFormat: gl.RGBA, + glDataType: gl.UNSIGNED_BYTE + }; }, uploadTextureFromSource: ({ texture, glFormat, glDataType }, source) => { const gl = this.gl; gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, glFormat, glFormat, glDataType, source); }, - uploadTextureFromData: ({ texture, glFormat, glDataType }, data, width, height) => { + uploadTextureFromData: ( + { texture, glFormat, glDataType }, + data, + width, + height + ) => { const gl = this.gl; gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D( @@ -180,19 +202,31 @@ class FilterRenderer2D { data ); }, - setTextureParams: (texture) => { - return setWebGLTextureParams(texture, this.gl, this._renderer.webglVersion); + setTextureParams: texture => { + return setWebGLTextureParams( + texture, + this.gl, + this._renderer.webglVersion + ); }, updateUniformValue: (shader, uniform, data) => { return setWebGLUniformValue( shader, uniform, data, - (tex) => this._renderer.getTexture(tex), + tex => this._renderer.getTexture(tex), this.gl ); }, - _enableAttrib: (_shader, attr, size, type, normalized, stride, offset) => { + _enableAttrib: ( + _shader, + attr, + size, + type, + normalized, + stride, + offset + ) => { const loc = attr.location; const gl = this.gl; // Enable register even if it is disabled @@ -210,7 +244,7 @@ class FilterRenderer2D { offset || 0 ); }, - _disableRemainingAttributes: (shader) => { + _disableRemainingAttributes: shader => { for (const location of this._renderer.registerEnabled.values()) { if ( !Object.keys(shader.attributes).some( @@ -231,8 +265,9 @@ class FilterRenderer2D { }, baseFilterShader: () => this.baseFilterShader(), strandsBackend: glslBackend, - getShaderHookTypes: (shader, hookName) => getShaderHookTypes(shader, hookName), - uniformNameFromHookKey: (key) => key.slice(key.indexOf(' ') + 1), + getShaderHookTypes: (shader, hookName) => + getShaderHookTypes(shader, hookName), + uniformNameFromHookKey: key => key.slice(key.indexOf(' ') + 1) }; this._baseFilterShader = undefined; @@ -303,15 +338,14 @@ class FilterRenderer2D { }` }, hookAliases: { - 'getColor': ['filterColor'], - }, + getColor: ['filterColor'] + } } ); } return this._baseFilterShader; } - getRandomFragmentShaderSnippet() { return randomGLSL; } @@ -331,8 +365,8 @@ class FilterRenderer2D { this.operation = operation; this.filterParameter = filterParameter; - let useDefaultParam = operation in filterParamDefaults && - filterParameter === undefined; + let useDefaultParam = + operation in filterParamDefaults && filterParameter === undefined; if (useDefaultParam) { this.filterParameter = filterParamDefaults[operation]; } @@ -353,7 +387,9 @@ class FilterRenderer2D { } if (!this.operation) { - console.error('No operation set for FilterRenderer2D, cannot initialize shader.'); + console.error( + 'No operation set for FilterRenderer2D, cannot initialize shader.' + ); return; } @@ -364,7 +400,11 @@ class FilterRenderer2D { } // Use the shared makeFilterShader function from filterShaders.js - const newShader = makeFilterShader(this._renderer, this.operation, this.parentRenderer._pInst); + const newShader = makeFilterShader( + this._renderer, + this.operation, + this.parentRenderer._pInst + ); this.filterShaders[this.operation] = newShader; this._shader = newShader; } @@ -393,8 +433,9 @@ class FilterRenderer2D { _renderPass() { const gl = this.gl; this._shader.bindShader('fill'); - const pixelDensity = this.parentRenderer.pixelDensity ? - this.parentRenderer.pixelDensity() : 1; + const pixelDensity = this.parentRenderer.pixelDensity + ? this.parentRenderer.pixelDensity() + : 1; const texelSize = [ 1 / (this.parentRenderer.width * pixelDensity), @@ -406,7 +447,10 @@ class FilterRenderer2D { // Set uniforms for the shader this._shader.setUniform('tex0', canvasTexture); this._shader.setUniform('texelSize', texelSize); - this._shader.setUniform('canvasSize', [this.parentRenderer.width, this.parentRenderer.height]); + this._shader.setUniform('canvasSize', [ + this.parentRenderer.width, + this.parentRenderer.height + ]); this._shader.setUniform('radius', Math.max(1, this.filterParameter)); this._shader.setUniform('filterParameter', this.filterParameter); this._shader.setDefaultUniforms(); @@ -416,11 +460,7 @@ class FilterRenderer2D { this.parentRenderer.blendMode(constants.BLEND); this.parentRenderer.resetMatrix(); - - const identityMatrix = [1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]; + const identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; this._shader.setUniform('uModelViewMatrix', identityMatrix); this._shader.setUniform('uProjectionMatrix', identityMatrix); @@ -461,8 +501,10 @@ class FilterRenderer2D { this.parentRenderer.clear(); this.parentRenderer.drawingContext.drawImage( this.canvas, - 0, 0, - this.parentRenderer.width, this.parentRenderer.height + 0, + 0, + this.parentRenderer.width, + this.parentRenderer.height ); // Vertical pass @@ -472,8 +514,10 @@ class FilterRenderer2D { this.parentRenderer.clear(); this.parentRenderer.drawingContext.drawImage( this.canvas, - 0, 0, - this.parentRenderer.width, this.parentRenderer.height + 0, + 0, + this.parentRenderer.width, + this.parentRenderer.height ); } else { // Single-pass filters @@ -483,11 +527,12 @@ class FilterRenderer2D { // con this.parentRenderer.blendMode(constants.BLEND); - this.parentRenderer.drawingContext.drawImage( this.canvas, - 0, 0, - this.parentRenderer.width, this.parentRenderer.height + 0, + 0, + this.parentRenderer.width, + this.parentRenderer.height ); } this.parentRenderer.pop(); diff --git a/src/image/filters.js b/src/image/filters.js index 0531fcc124..8f1edd9fd8 100644 --- a/src/image/filters.js +++ b/src/image/filters.js @@ -16,7 +16,6 @@ */ const Filters = { - /* * Helper functions */ @@ -42,7 +41,8 @@ const Filters = { return canvas .getContext('2d') .getImageData(0, 0, canvas.width, canvas.height).data; - } else if (canvas.getContext('webgl')) { //Check WebGL context support + } else if (canvas.getContext('webgl')) { + //Check WebGL context support const gl = canvas.getContext('webgl'); // Calculate the size of pixel data // (4 bytes per pixel - one byte for each RGBA channel). @@ -107,7 +107,6 @@ const Filters = { } }, - /** * Returns the ImageData object for a canvas. * https://developer.mozilla.org/en-US/docs/Web/API/ImageData @@ -128,7 +127,6 @@ const Filters = { } }, - /** * Returns a blank ImageData object. * @@ -166,7 +164,11 @@ const Filters = { apply(canvas, func, filterParam) { const pixelsState = canvas.getContext('2d'); const imageData = pixelsState.getImageData( - 0, 0, canvas.width, canvas.height); + 0, + 0, + canvas.width, + canvas.height + ); //Filters can either return a new ImageData object, or just modify //the one they received. @@ -182,7 +184,8 @@ const Filters = { canvas.width, canvas.height ); - } else { //Restore the original pixel. + } else { + //Restore the original pixel. pixelsState.putImageData( imageData, 0, @@ -312,9 +315,9 @@ const Filters = { const blevel = pixels[i + 2]; // New pixel value by posterizing each color. - pixels[i] = ((rlevel * level) >> 8) * 255 / levels1; - pixels[i + 1] = ((glevel * level) >> 8) * 255 / levels1; - pixels[i + 2] = ((blevel * level) >> 8) * 255 / levels1; + pixels[i] = (((rlevel * level) >> 8) * 255) / levels1; + pixels[i + 1] = (((glevel * level) >> 8) * 255) / levels1; + pixels[i + 2] = (((blevel * level) >> 8) * 255) / levels1; } }, @@ -659,6 +662,4 @@ function blurARGB(canvas, radius) { Filters._setPixels(pixels, argb); } - - export default Filters; diff --git a/src/image/image.js b/src/image/image.js index 2296f5a414..b07d946d8c 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -12,7 +12,7 @@ import * as omggif from 'omggif'; import { Element } from '../dom/p5.Element'; import { Framebuffer } from '../webgl/p5.Framebuffer'; -function image(p5, fn){ +function image(p5, fn) { /** * Creates a new p5.Image object. * @@ -136,7 +136,7 @@ function image(p5, fn){ * describe('A black square drawn in the middle of a gray square.'); * } */ - fn.createImage = function(width, height) { + fn.createImage = function (width, height) { // p5._validateParameters('createImage', arguments); return new p5.Image(width, height); }; @@ -247,7 +247,7 @@ function image(p5, fn){ * @param {String} [filename] * @param {String} [extension] */ - fn.saveCanvas = function(...args) { + fn.saveCanvas = function (...args) { // copy arguments to array let htmlCanvas, filename, extension, temporaryGraphics; @@ -259,8 +259,10 @@ function image(p5, fn){ args.shift(); } else if (args[0] instanceof Framebuffer) { const framebuffer = args[0]; - temporaryGraphics = this.createGraphics(framebuffer.width, - framebuffer.height); + temporaryGraphics = this.createGraphics( + framebuffer.width, + framebuffer.height + ); temporaryGraphics.pixelDensity(framebuffer.pixelDensity()); framebuffer.loadPixels(); temporaryGraphics.loadPixels(); @@ -281,9 +283,7 @@ function image(p5, fn){ } extension = - extension || - fn._checkFileExtension(filename, extension)[1] || - 'png'; + extension || fn._checkFileExtension(filename, extension)[1] || 'png'; let mimeType; switch (extension) { @@ -302,14 +302,14 @@ function image(p5, fn){ htmlCanvas.toBlob(blob => { fn.downloadFile(blob, filename, extension); - if(temporaryGraphics) temporaryGraphics.remove(); + if (temporaryGraphics) temporaryGraphics.remove(); }, mimeType); }; // this is the old saveGif, left here for compatibility purposes // the only place I found it being used was on image/p5.Image.js, on the // save function. that has been changed to use this function. - fn.encodeAndDownloadGif = function(pImg, filename) { + fn.encodeAndDownloadGif = function (pImg, filename) { const props = pImg.gifProperties; //convert loopLimit back into Netscape Block formatting @@ -362,13 +362,11 @@ function image(p5, fn){ // Now to build the global palette // Sort all the unique palettes in descending order of their occurrence - const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames) - .sort(function( - a, - b - ) { + const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames).sort( + function (a, b) { return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq; - }); + } + ); // The initial global palette is the one with the most occurrence const globalPalette = palettesSortedByFreq[0] @@ -630,7 +628,7 @@ function image(p5, fn){ * } * } */ - fn.saveFrames = function(fName, ext, _duration, _fps, callback) { + fn.saveFrames = function (fName, ext, _duration, _fps, callback) { // p5._validateParameters('saveFrames', arguments); let duration = _duration || 3; duration = Math.max(Math.min(duration, 15), 0); @@ -660,7 +658,7 @@ function image(p5, fn){ }, duration + 0.01); }; - fn._makeFrame = function(filename, extension, _cnv) { + fn._makeFrame = function (filename, extension, _cnv) { let cnv; if (this) { cnv = this._curElement.elt; @@ -701,6 +699,6 @@ function image(p5, fn){ export default image; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { image(p5, p5.prototype); } diff --git a/src/image/index.js b/src/image/index.js index ce63a52bd4..cd9a7f1ae8 100644 --- a/src/image/index.js +++ b/src/image/index.js @@ -5,7 +5,7 @@ import pixels from './pixels.js'; import shader from '../webgl/p5.Shader.js'; import texture from '../webgl/p5.Texture.js'; -export default function(p5){ +export default function (p5) { p5.registerAddon(image); p5.registerAddon(loadingDisplaying); p5.registerAddon(p5image); diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 1f5077989a..0aae6c324a 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -10,7 +10,7 @@ import { request } from '../io/files'; import * as omggif from 'omggif'; import { GIFEncoder, quantize, nearestColorIndex } from 'gifenc'; -function loadingDisplaying(p5, fn){ +function loadingDisplaying(p5, fn) { /** * Loads an image to create a p5.Image object. * @@ -91,14 +91,10 @@ function loadingDisplaying(p5, fn){ * console.error('Oops!', event); * } */ - fn.loadImage = async function( - path, - successCallback, - failureCallback - ) { + fn.loadImage = async function (path, successCallback, failureCallback) { // p5._validateParameters('loadImage', arguments); - try{ + try { let pImg = new p5.Image(1, 1, this); const req = new Request(path, { @@ -118,11 +114,7 @@ function loadingDisplaying(p5, fn){ } if (contentType && contentType.includes('image/gif')) { - await _createGif( - data, - pImg - ); - + await _createGif(data, pImg); } else { // Non-GIF Section const img = await new Promise((resolve, reject) => { @@ -152,15 +144,14 @@ function loadingDisplaying(p5, fn){ const cb = () => { pImg.modified = true; - if(successCallback){ + if (successCallback) { return successCallback(pImg); - }else{ + } else { return pImg; } }; return this._internal ? this._internal(cb) : cb(); - - } catch(err) { + } catch (err) { // p5._friendlyFileLoadError(0, path); // TODO: Additional general error message specific to image error // File error is already taken cared of by `request` @@ -248,7 +239,7 @@ function loadingDisplaying(p5, fn){ * } * } */ - fn.saveGif = async function( + fn.saveGif = async function ( fileName, duration, options = { @@ -269,12 +260,13 @@ function loadingDisplaying(p5, fn){ } // extract variables for more comfortable use - const delay = (options && options.delay) || 0; // in seconds - const units = (options && options.units) || 'seconds'; // either 'seconds' or 'frames' + const delay = (options && options.delay) || 0; // in seconds + const units = (options && options.units) || 'seconds'; // either 'seconds' or 'frames' const silent = (options && options.silent) || false; const notificationDuration = (options && options.notificationDuration) || 0; const notificationID = (options && options.notificationID) || 'progressBar'; - const resetAnimation = (options && options.reset !== undefined) ? options.reset : true; + const resetAnimation = + options && options.reset !== undefined ? options.reset : true; // if arguments in the options object are not correct, cancel operation if (typeof delay !== 'number') { throw TypeError('Delay parameter must be a number'); @@ -314,7 +306,7 @@ function loadingDisplaying(p5, fn){ // this delay has nothing to do with the // delay in options, but rather is the delay // we have to specify to the gif encoder between frames. - let gifFrameDelay = 1 / _frameRate * 1000; + let gifFrameDelay = (1 / _frameRate) * 1000; // constrain it to be always greater than 20, // otherwise it won't work in some browsers and systems @@ -349,7 +341,7 @@ function loadingDisplaying(p5, fn){ document.getElementById(notificationID).remove(); let p; - if (!silent){ + if (!silent) { p = this.createP(''); p.id(notificationID); p.style('font-size', '16px'); @@ -411,8 +403,12 @@ function loadingDisplaying(p5, fn){ data = _flipPixels(pixels, this.width, this.height); } else { - data = this.drawingContext.getImageData(0, 0, this.width, this.height) - .data; + data = this.drawingContext.getImageData( + 0, + 0, + this.width, + this.height + ).data; } frames.push(data); @@ -421,9 +417,9 @@ function loadingDisplaying(p5, fn){ if (!silent) { p.html( 'Saved frame ' + - frames.length.toString() + - ' out of ' + - nFrames.toString() + frames.length.toString() + + ' out of ' + + nFrames.toString() ); } await new Promise(resolve => setTimeout(resolve, 0)); @@ -508,11 +504,13 @@ function loadingDisplaying(p5, fn){ if (!silent) { p.html( - 'Rendered frame ' + i.toString() + ' out of ' + nFrames.toString() + 'Rendered frame ' + + i.toString() + + ' out of ' + + nFrames.toString() ); } - // this just makes the process asynchronous, preventing // that the encoding locks up the browser await new Promise(resolve => setTimeout(resolve, 0)); @@ -532,9 +530,9 @@ function loadingDisplaying(p5, fn){ this._recording = false; this.loop(); - if (!silent){ + if (!silent) { p.html('Done. Downloading your gif!🌸'); - if(notificationDuration > 0) + if (notificationDuration > 0) setTimeout(() => p.remove(), notificationDuration * 1000); } @@ -632,7 +630,8 @@ function loadingDisplaying(p5, fn){ gifReader.decodeAndBlitFrameRGBA(frameNum, framePixels); } catch (e) { // p5._friendlyFileLoadError(8, pImg.src); - p5.FES.log`There was some trouble loading your GIF. Make sure that your GIF is using 87a or 89a encoding.`(); + p5.FES + .log`There was some trouble loading your GIF. Make sure that your GIF is using 87a or 89a encoding.`(); throw e; } }; @@ -813,10 +812,14 @@ function loadingDisplaying(p5, fn){ function _imageFit(fit, xAlign, yAlign, dx, dy, dw, dh, sx, sy, sw, sh) { if (fit === constants.COVER) { const { x, y, w, h } = _imageCover( - xAlign, yAlign, - dw, dh, - sx, sy, - sw, sh + xAlign, + yAlign, + dw, + dh, + sx, + sy, + sw, + sh ); sx = x; sy = y; @@ -1036,7 +1039,7 @@ function loadingDisplaying(p5, fn){ * @param {(LEFT|RIGHT|CENTER)} [xAlign=CENTER] either LEFT, RIGHT or CENTER default is CENTER * @param {(TOP|BOTTOM|CENTER)} [yAlign=CENTER] either TOP, BOTTOM or CENTER default is CENTER */ - fn.image = function( + fn.image = function ( img, dx, dy, @@ -1104,8 +1107,10 @@ function loadingDisplaying(p5, fn){ _sw *= pd; let vals = canvas.modeAdjust( - _dx, _dy, - _dw, _dh, + _dx, + _dy, + _dw, + _dh, this._renderer.states.imageMode ); vals = _imageFit( @@ -1266,11 +1271,10 @@ function loadingDisplaying(p5, fn){ * @method tint * @return {p5.Color} the current tint color */ - fn.tint = function(...args) { + fn.tint = function (...args) { if (args.length === 0) { return this._renderer.states.tint; // getter - } - else { + } else { this._renderer.states.setValue('tint', this.color(...args)); return this; } @@ -1305,7 +1309,7 @@ function loadingDisplaying(p5, fn){ * describe('Two images of an umbrella and a ceiling side-by-side. The image on the left has a red tint.'); * } */ - fn.noTint = function() { + fn.noTint = function () { this._renderer.states.setValue('tint', null); return this; }; @@ -1408,9 +1412,10 @@ function loadingDisplaying(p5, fn){ * @method imageMode * @return {(CORNER|CORNERS|CENTER)} the current image mode */ - fn.imageMode = function(m) { + fn.imageMode = function (m) { // p5._validateParameters('imageMode', arguments); - if (typeof m === 'undefined') { // getter + if (typeof m === 'undefined') { + // getter return this._renderer.states.imageMode; } if ( @@ -1425,6 +1430,6 @@ function loadingDisplaying(p5, fn){ export default loadingDisplaying; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { loadingDisplaying(p5, p5.prototype); } diff --git a/src/image/p5.Image.js b/src/image/p5.Image.js index 6107cfaa5a..423d7f1cf8 100644 --- a/src/image/p5.Image.js +++ b/src/image/p5.Image.js @@ -46,7 +46,7 @@ class Image { */ pixelDensity(density) { if (typeof density !== 'undefined') { - // Setter: set the density and handle resize + // Setter: set the density and handle resize if (density <= 0) { const errorObj = { type: 'INVALID_VALUE', @@ -68,7 +68,7 @@ class Image { return this; // Return the image instance for chaining if needed } else { - // Getter: return the default density + // Getter: return the default density return this._pixelDensity; } } @@ -413,7 +413,7 @@ class Image { const canvas = this.canvas; if (typeof x === 'undefined' && typeof y === 'undefined') { - // get() + // get() x = y = 0; w = pixelsState.width; h = pixelsState.height; @@ -422,21 +422,21 @@ class Image { y *= pd; if (typeof w === 'undefined' && typeof h === 'undefined') { - // get(x,y) + // get(x,y) if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) { return [0, 0, 0, 0]; } return this._getPixel(x, y); } - // get(x,y,w,h) + // get(x,y,w,h) } - const region = new Image(w*pd, h*pd); + const region = new Image(w * pd, h * pd); region.pixelDensity(pd); region.canvas .getContext('2d') - .drawImage(canvas, x, y, w * pd, h * pd, 0, 0, w*pd, h*pd); + .drawImage(canvas, x, y, w * pd, h * pd, 0, 0, w * pd, h * pd); return region; } @@ -582,10 +582,7 @@ class Image { if (imgOrCol instanceof Image) { this.drawingContext.save(); this.drawingContext.setTransform(1, 0, 0, 1, 0, 0); - this.drawingContext.scale( - this._pixelDensity, - this._pixelDensity - ); + this.drawingContext.scale(this._pixelDensity, this._pixelDensity); this.drawingContext.clearRect(x, y, imgOrCol.width, imgOrCol.height); this.drawingContext.drawImage(imgOrCol.canvas, x, y); this.drawingContext.restore(); @@ -596,9 +593,7 @@ class Image { a = 0; let idx = 4 * - (y * - this._pixelDensity * - (this.width * this._pixelDensity) + + (y * this._pixelDensity * (this.width * this._pixelDensity) + x * this._pixelDensity); if (!pixelsState.imageData) { pixelsState.loadPixels(); @@ -634,9 +629,7 @@ class Image { // loop over idx = 4 * - ((y * this._pixelDensity + j) * - this.width * - this._pixelDensity + + ((y * this._pixelDensity + j) * this.width * this._pixelDensity + (x * this._pixelDensity + i)); pixelsState.pixels[idx] = r; pixelsState.pixels[idx + 1] = g; @@ -737,9 +730,9 @@ class Image { width = this.canvas.width; height = this.canvas.height; } else if (width === 0) { - width = this.canvas.width * height / this.canvas.height; + width = (this.canvas.width * height) / this.canvas.height; } else if (height === 0) { - height = this.canvas.height * width / this.canvas.width; + height = (this.canvas.height * width) / this.canvas.width; } width = Math.floor(width); @@ -756,8 +749,8 @@ class Image { let pos = 0; for (let y = 0; y < dst.height; y++) { for (let x = 0; x < dst.width; x++) { - const srcX = Math.floor(x * src.width / dst.width); - const srcY = Math.floor(y * src.height / dst.height); + const srcX = Math.floor((x * src.width) / dst.width); + const srcY = Math.floor((y * src.height) / dst.height); let srcPos = (srcY * src.width + srcX) * 4; dst.data[pos++] = src.data[srcPos++]; // R dst.data[pos++] = src.data[srcPos++]; // G @@ -776,11 +769,19 @@ class Image { } } - tempCanvas.getContext('2d').drawImage( - this.canvas, - 0, 0, this.canvas.width, this.canvas.height, - 0, 0, tempCanvas.width, tempCanvas.height - ); + tempCanvas + .getContext('2d') + .drawImage( + this.canvas, + 0, + 0, + this.canvas.width, + this.canvas.height, + 0, + 0, + tempCanvas.width, + tempCanvas.height + ); // Resize the original canvas, which will clear its contents this.canvas.width = this.width = width; @@ -789,8 +790,14 @@ class Image { //Copy the image back this.drawingContext.drawImage( tempCanvas, - 0, 0, width, height, - 0, 0, width, height + 0, + 0, + width, + height, + 0, + 0, + width, + height ); if (this.pixels.length > 0) { @@ -915,18 +922,7 @@ class Image { this._copyHelper(this, srcImage, sx, sy, sw, sh, dx, dy, dw, dh); } - _copyHelper( - dstImage, - srcImage, - sx, - sy, - sw, - sh, - dx, - dy, - dw, - dh - ){ + _copyHelper(dstImage, srcImage, sx, sy, sw, sh, dx, dy, dw, dh) { const s = srcImage.canvas.width / srcImage.width; // adjust coord system for 3D when renderer // ie top-left = -width/2, -height/2 @@ -1452,9 +1448,7 @@ class Image { } else { let htmlCanvas = this.canvas; extension = - extension || - _checkFileExtension(filename, extension)[1] || - 'png'; + extension || _checkFileExtension(filename, extension)[1] || 'png'; let mimeType; switch (extension) { @@ -1799,7 +1793,7 @@ class Image { } } } -}; +} function encodeAndDownloadGif(pImg, filename) { const props = pImg.gifProperties; @@ -1854,12 +1848,11 @@ function encodeAndDownloadGif(pImg, filename) { // Now to build the global palette // Sort all the unique palettes in descending order of their occurrence - const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames).sort(function( - a, - b - ) { - return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq; - }); + const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames).sort( + function (a, b) { + return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq; + } + ); // The initial global palette is the one with the most occurrence const globalPalette = palettesSortedByFreq[0] @@ -2024,9 +2017,9 @@ function encodeAndDownloadGif(pImg, filename) { type: 'image/gif' }); downloadFile(blob, filename, extension); -}; +} -function image(p5, fn){ +function image(p5, fn) { /** * A class to describe an image. * @@ -2268,6 +2261,6 @@ function image(p5, fn){ export default image; export { Image }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { image(p5, p5.prototype); } diff --git a/src/image/pixels.js b/src/image/pixels.js index 50e315166a..f20b688d25 100644 --- a/src/image/pixels.js +++ b/src/image/pixels.js @@ -6,7 +6,7 @@ import Filters from './filters'; -function pixels(p5, fn){ +function pixels(p5, fn) { /** * Copies a region of pixels from one image to another. * @@ -122,7 +122,7 @@ function pixels(p5, fn){ * @param {Integer} dh * @param {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode */ - fn.blend = function(...args) { + fn.blend = function (...args) { // p5._validateParameters('blend', args); if (this._renderer) { this._renderer.blend(...args); @@ -193,7 +193,7 @@ function pixels(p5, fn){ * @param {Integer} dw * @param {Integer} dh */ - fn.copy = function(...args) { + fn.copy = function (...args) { let srcImage, sx, sy, sw, sh, dx, dy, dw, dh; if (args.length === 9) { srcImage = args[0]; @@ -222,18 +222,7 @@ function pixels(p5, fn){ fn._copyHelper(this, srcImage, sx, sy, sw, sh, dx, dy, dw, dh); }; - fn._copyHelper = ( - dstImage, - srcImage, - sx, - sy, - sw, - sh, - dx, - dy, - dw, - dh - ) => { + fn._copyHelper = (dstImage, srcImage, sx, sy, sw, sh, dx, dy, dw, dh) => { const s = srcImage.canvas.width / srcImage.width; // adjust coord system for 3D when renderer // ie top-left = -width/2, -height/2 @@ -499,7 +488,7 @@ function pixels(p5, fn){ * @private * @returns {p5.Graphics} */ - fn.getFilterGraphicsLayer = function() { + fn.getFilterGraphicsLayer = function () { return this._renderer.getFilterGraphicsLayer(); }; @@ -514,7 +503,7 @@ function pixels(p5, fn){ * @param {p5.Shader} shaderFilter shader that's been loaded, with the * frag shader using a `tex0` uniform. */ - fn.filter = function(...args) { + fn.filter = function (...args) { // p5._validateParameters('filter', args); let { shader, operation, value, useWebGL } = parseFilterArgs(...args); @@ -535,7 +524,7 @@ function pixels(p5, fn){ return; } - if(!useWebGL && this._renderer.isP3D) { + if (!useWebGL && this._renderer.isP3D) { console.warn('filter() with useWebGL=false is not supported in WEBGL'); } @@ -546,7 +535,6 @@ function pixels(p5, fn){ // when this is P2D renderer, create/use hidden webgl renderer else { - if (shader) { this._renderer.filterRenderer.setOperation(operation, value, shader); } else { @@ -573,8 +561,7 @@ function pixels(p5, fn){ if (args[0] instanceof p5.Shader) { result.shader = args[0]; return result; - } - else { + } else { result.operation = args[0]; } @@ -582,7 +569,7 @@ function pixels(p5, fn){ result.value = args[1]; } - if (args[args.length-1] === false) { + if (args[args.length - 1] === false) { result.useWebGL = false; } return result; @@ -693,7 +680,7 @@ function pixels(p5, fn){ * @param {Number} y * @return {Number[]} color of the pixel at (x, y) in array format `[R, G, B, A]`. */ - fn.get = function(x, y, w, h) { + fn.get = function (x, y, w, h) { // p5._validateParameters('get', arguments); return this._renderer.get(...arguments); }; @@ -738,7 +725,7 @@ function pixels(p5, fn){ * describe('Two identical images of mountain landscapes, one on top of the other.'); * } */ - fn.loadPixels = function(...args) { + fn.loadPixels = function (...args) { // p5._validateParameters('loadPixels', args); return this._renderer.loadPixels(); }; @@ -845,7 +832,7 @@ function pixels(p5, fn){ * describe('An image of a mountain landscape.'); * } */ - fn.set = function(x, y, imgOrCol) { + fn.set = function (x, y, imgOrCol) { this._renderer.set(x, y, imgOrCol); }; @@ -897,7 +884,7 @@ function pixels(p5, fn){ * describe('Two identical images of mountain landscapes, one on top of the other.'); * } */ - fn.updatePixels = function(x, y, w, h) { + fn.updatePixels = function (x, y, w, h) { // p5._validateParameters('updatePixels', arguments); // graceful fail - if loadPixels() or set() has not been called, pixel // array will be empty, ignore call to updatePixels() @@ -1036,6 +1023,6 @@ function pixels(p5, fn){ export default pixels; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { pixels(p5, p5.prototype); } diff --git a/src/io/csv.js b/src/io/csv.js index 1714ee2a7f..0ab51d7ffc 100644 --- a/src/io/csv.js +++ b/src/io/csv.js @@ -22,7 +22,7 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export function parse (csv, options, reviver = v => v) { +export function parse(csv, options, reviver = v => v) { const ctx = Object.create(null); ctx.options = options || {}; ctx.reviver = reviver; @@ -32,15 +32,24 @@ export function parse (csv, options, reviver = v => v) { ctx.col = 1; ctx.row = 1; - ctx.options.delimiter = ctx.options.delimiter === undefined ? '"' : options.delimiter; - if(ctx.options.delimiter.length > 1 || ctx.options.delimiter.length === 0) - throw Error(`CSVError: delimiter must be one character [${ctx.options.separator}]`); - - ctx.options.separator = ctx.options.separator === undefined ? ',' : options.separator; - if(ctx.options.separator.length > 1 || ctx.options.separator.length === 0) - throw Error(`CSVError: separator must be one character [${ctx.options.separator}]`); - - const lexer = new RegExp(`${escapeRegExp(ctx.options.delimiter)}|${escapeRegExp(ctx.options.separator)}|\r\n|\n|\r|[^${escapeRegExp(ctx.options.delimiter)}${escapeRegExp(ctx.options.separator)}\r\n]+`, 'y'); + ctx.options.delimiter = + ctx.options.delimiter === undefined ? '"' : options.delimiter; + if (ctx.options.delimiter.length > 1 || ctx.options.delimiter.length === 0) + throw Error( + `CSVError: delimiter must be one character [${ctx.options.separator}]` + ); + + ctx.options.separator = + ctx.options.separator === undefined ? ',' : options.separator; + if (ctx.options.separator.length > 1 || ctx.options.separator.length === 0) + throw Error( + `CSVError: separator must be one character [${ctx.options.separator}]` + ); + + const lexer = new RegExp( + `${escapeRegExp(ctx.options.delimiter)}|${escapeRegExp(ctx.options.separator)}|\r\n|\n|\r|[^${escapeRegExp(ctx.options.delimiter)}${escapeRegExp(ctx.options.separator)}\r\n]+`, + 'y' + ); const isNewline = /^(\r\n|\n|\r)$/; let matches = []; @@ -84,7 +93,9 @@ export function parse (csv, options, reviver = v => v) { break; default: state = 4; - throw Error(`CSVError: Illegal state [row:${ctx.row}, col:${ctx.col}]`); + throw Error( + `CSVError: Illegal state [row:${ctx.row}, col:${ctx.col}]` + ); } break; case 3: // delimited input @@ -114,7 +125,9 @@ export function parse (csv, options, reviver = v => v) { entryEnd(ctx); break; default: - throw Error(`CSVError: Illegal state [row:${ctx.row}, col:${ctx.col}]`); + throw Error( + `CSVError: Illegal state [row:${ctx.row}, col:${ctx.col}]` + ); } break; } @@ -129,7 +142,7 @@ export function parse (csv, options, reviver = v => v) { return ctx.output; } -export function stringify (array, options = {}, replacer = v => v) { +export function stringify(array, options = {}, replacer = v => v) { const ctx = Object.create(null); ctx.options = options; ctx.options.eof = ctx.options.eof !== undefined ? ctx.options.eof : true; @@ -137,23 +150,36 @@ export function stringify (array, options = {}, replacer = v => v) { ctx.col = 1; ctx.output = ''; - ctx.options.delimiter = ctx.options.delimiter === undefined ? '"' : options.delimiter; - if(ctx.options.delimiter.length > 1 || ctx.options.delimiter.length === 0) - throw Error(`CSVError: delimiter must be one character [${ctx.options.separator}]`); + ctx.options.delimiter = + ctx.options.delimiter === undefined ? '"' : options.delimiter; + if (ctx.options.delimiter.length > 1 || ctx.options.delimiter.length === 0) + throw Error( + `CSVError: delimiter must be one character [${ctx.options.separator}]` + ); - ctx.options.separator = ctx.options.separator === undefined ? ',' : options.separator; - if(ctx.options.separator.length > 1 || ctx.options.separator.length === 0) - throw Error(`CSVError: separator must be one character [${ctx.options.separator}]`); + ctx.options.separator = + ctx.options.separator === undefined ? ',' : options.separator; + if (ctx.options.separator.length > 1 || ctx.options.separator.length === 0) + throw Error( + `CSVError: separator must be one character [${ctx.options.separator}]` + ); - const needsDelimiters = new RegExp(`${escapeRegExp(ctx.options.delimiter)}|${escapeRegExp(ctx.options.separator)}|\r\n|\n|\r`); + const needsDelimiters = new RegExp( + `${escapeRegExp(ctx.options.delimiter)}|${escapeRegExp(ctx.options.separator)}|\r\n|\n|\r` + ); array.forEach((row, rIdx) => { let entry = ''; ctx.col = 1; row.forEach((col, cIdx) => { if (typeof col === 'string') { - col = col.replace(new RegExp(ctx.options.delimiter, 'g'), `${ctx.options.delimiter}${ctx.options.delimiter}`); - col = needsDelimiters.test(col) ? `${ctx.options.delimiter}${col}${ctx.options.delimiter}` : col; + col = col.replace( + new RegExp(ctx.options.delimiter, 'g'), + `${ctx.options.delimiter}${ctx.options.delimiter}` + ); + col = needsDelimiters.test(col) + ? `${ctx.options.delimiter}${col}${ctx.options.delimiter}` + : col; } entry += replacer(col, ctx.row, ctx.col); if (cIdx !== row.length - 1) { @@ -176,21 +202,21 @@ export function stringify (array, options = {}, replacer = v => v) { return ctx.output; } -function valueEnd (ctx) { +function valueEnd(ctx) { const value = ctx.options.typed ? inferType(ctx.value) : ctx.value; ctx.entry.push(ctx.reviver(value, ctx.row, ctx.col)); ctx.value = ''; ctx.col++; } -function entryEnd (ctx) { +function entryEnd(ctx) { ctx.output.push(ctx.entry); ctx.entry = []; ctx.row++; ctx.col = 1; } -function inferType (value) { +function inferType(value) { const isNumber = /.\./; switch (true) { diff --git a/src/io/files.js b/src/io/files.js index d71468d98a..8dce5b6839 100644 --- a/src/io/files.js +++ b/src/io/files.js @@ -23,7 +23,7 @@ export async function request(path, type) { if (res.ok) { let data; - switch(type) { + switch (type) { case 'json': errorFileType = TL.tl`JSON file`; data = await res.json(); @@ -46,7 +46,6 @@ export async function request(path, type) { } return { data, headers: res.headers }; - } else { const err = new HTTPError(res.statusText); err.status = res.status; @@ -55,8 +54,7 @@ export async function request(path, type) { throw err; } - - } catch(err) { + } catch (err) { // Handle both fetch error and HTTP error if (err instanceof TypeError) { console.log('You may have encountered a CORS error'); @@ -66,14 +64,16 @@ export async function request(path, type) { + More info: ${infoURL}`(); } else if (err instanceof SyntaxError) { - console.log('There is an error parsing the response to requested data structure'); + console.log( + 'There is an error parsing the response to requested data structure' + ); } throw err; } } -function files(p5, fn){ +function files(p5, fn) { /** * Loads a JSON file to create an `Object`. * @@ -274,7 +274,7 @@ function files(p5, fn){ * } */ fn.loadJSON = async function (path, successCallback, errorCallback) { - try{ + try { const { data } = await request(path, 'json'); const cb = () => { if (successCallback) return successCallback(data); @@ -283,7 +283,7 @@ function files(p5, fn){ return this._internal ? this._internal(cb) : cb(); } catch (err) { // p5._friendlyFileLoadError(5, path); - if(errorCallback) { + if (errorCallback) { return errorCallback(err); } else { throw err; @@ -413,7 +413,7 @@ function files(p5, fn){ fn.loadStrings = async function (path, successCallback, errorCallback) { // p5._validateParameters('loadStrings', arguments); - try{ + try { let { data } = await request(path, 'text'); const cb = () => { data = data.split(/\r?\n/); @@ -421,9 +421,9 @@ function files(p5, fn){ return data; }; return this._internal ? this._internal(cb) : cb(); - } catch(err) { + } catch (err) { // p5._friendlyFileLoadError(3, path); - if(errorCallback) { + if (errorCallback) { return errorCallback(err); } else { throw err; @@ -490,19 +490,19 @@ function files(p5, fn){ successCallback, errorCallback ) { - if(typeof arguments[arguments.length-1] === 'function'){ - if(typeof arguments[arguments.length-2] === 'function'){ - successCallback = arguments[arguments.length-2]; - errorCallback = arguments[arguments.length-1]; - }else{ - successCallback = arguments[arguments.length-1]; + if (typeof arguments[arguments.length - 1] === 'function') { + if (typeof arguments[arguments.length - 2] === 'function') { + successCallback = arguments[arguments.length - 2]; + errorCallback = arguments[arguments.length - 1]; + } else { + successCallback = arguments[arguments.length - 1]; } } - if(typeof separator !== 'string') separator = ','; - if(typeof header === 'function') header = false; + if (typeof separator !== 'string') separator = ','; + if (typeof header === 'function') header = false; - try{ + try { let { data } = await request(path, 'text'); const cb = () => { let ret = new p5.Table(); @@ -510,9 +510,9 @@ function files(p5, fn){ separator }); - if(header){ + if (header) { ret.columns = data.shift(); - }else{ + } else { ret.columns = Array(data[0].length).fill(null); } @@ -528,9 +528,9 @@ function files(p5, fn){ } }; return this._internal ? this._internal(cb) : cb(); - } catch(err) { + } catch (err) { // p5._friendlyFileLoadError(2, path); - if(errorCallback) { + if (errorCallback) { return errorCallback(err); } else { throw err; @@ -687,7 +687,7 @@ function files(p5, fn){ * } */ fn.loadXML = async function (path, successCallback, errorCallback) { - try{ + try { const parser = new DOMParser(); let { data } = await request(path, 'text'); @@ -698,9 +698,9 @@ function files(p5, fn){ return data; }; return this._internal ? this._internal(cb) : cb(); - } catch(err) { + } catch (err) { // p5._friendlyFileLoadError(1, path); - if(errorCallback) { + if (errorCallback) { return errorCallback(err); } else { throw err; @@ -739,7 +739,7 @@ function files(p5, fn){ * } */ fn.loadBytes = async function (path, successCallback, errorCallback) { - try{ + try { let { data } = await request(path, 'arrayBuffer'); const cb = () => { data = new Uint8Array(data); @@ -747,9 +747,9 @@ function files(p5, fn){ return data; }; return this._internal ? this._internal(cb) : cb(); - } catch(err) { + } catch (err) { // p5._friendlyFileLoadError(6, path); - if(errorCallback) { + if (errorCallback) { return errorCallback(err); } else { throw err; @@ -796,16 +796,16 @@ function files(p5, fn){ * } * } */ - fn.loadBlob = async function(path, successCallback, errorCallback) { - try{ + fn.loadBlob = async function (path, successCallback, errorCallback) { + try { const { data } = await request(path, 'blob'); const cb = () => { if (successCallback) return successCallback(data); return data; }; return this._internal ? this._internal(cb) : cb(); - } catch(err) { - if(errorCallback) { + } catch (err) { + if (errorCallback) { return errorCallback(err); } else { throw err; @@ -868,7 +868,12 @@ function files(p5, fn){ * @param {Function} [errorCallback] * @return {Promise} */ - fn.httpGet = async function (path, datatype='text', successCallback, errorCallback) { + fn.httpGet = async function ( + path, + datatype = 'text', + successCallback, + errorCallback + ) { // p5._validateParameters('httpGet', arguments); if (typeof datatype === 'function') { @@ -960,7 +965,13 @@ function files(p5, fn){ * @param {Function} [errorCallback] * @return {Promise} */ - fn.httpPost = async function (path, data, datatype='text', successCallback, errorCallback) { + fn.httpPost = async function ( + path, + data, + datatype = 'text', + successCallback, + errorCallback + ) { // p5._validateParameters('httpPost', arguments); // This behave similarly to httpGet and additional options should be passed @@ -973,7 +984,6 @@ function files(p5, fn){ errorCallback = datatype; data = undefined; datatype = 'text'; - } else if (typeof datatype === 'function') { // Data is provided but not datatype\ errorCallback = successCallback; @@ -984,14 +994,12 @@ function files(p5, fn){ let reqData = data; let contentType = 'text/plain'; // Normalize data - if(data instanceof p5.XML) { + if (data instanceof p5.XML) { reqData = data.serialize(); contentType = 'application/xml'; - - } else if(data instanceof p5.Image) { + } else if (data instanceof p5.Image) { reqData = await data.toBlob(); contentType = 'image/png'; - } else if (typeof data === 'object') { reqData = JSON.stringify(data); contentType = 'application/json'; @@ -1110,18 +1118,19 @@ function files(p5, fn){ // will most likely want to pass in a Request to path, the only convenience // is that datatype will be taken into account to parse the response. - if(typeof datatype === 'function'){ + if (typeof datatype === 'function') { errorCallback = successCallback; successCallback = datatype; datatype = undefined; } // Try to infer data type if it is defined - if(!datatype){ - const extension = typeof path === 'string' ? - path.split('.').pop() : - path.url.split('.').pop(); - switch(extension) { + if (!datatype) { + const extension = + typeof path === 'string' + ? path.split('.').pop() + : path.url.split('.').pop(); + switch (extension) { case 'json': datatype = 'json'; break; @@ -1135,8 +1144,8 @@ function files(p5, fn){ break; case 'xml': - // NOTE: still need to normalize type handling/mapping - // datatype = 'xml'; + // NOTE: still need to normalize type handling/mapping + // datatype = 'xml'; case 'txt': default: datatype = 'text'; @@ -1147,15 +1156,15 @@ function files(p5, fn){ method }); - try{ + try { const { data } = await request(req, datatype); if (successCallback) { return successCallback(data); } else { return data; } - } catch(err) { - if(errorCallback) { + } catch (err) { + if (errorCallback) { return errorCallback(err); } else { throw err; @@ -1642,17 +1651,14 @@ function files(p5, fn){ if (args.length === 0) { fn.saveCanvas(cnv); return; - } else if (args[0] instanceof Renderer || args[0] instanceof Graphics) { // otherwise, parse the arguments // if first param is a p5Graphics, then saveCanvas fn.saveCanvas(args[0].canvas, args[1], args[2]); return; - } else if (args.length === 1 && typeof args[0] === 'string') { // if 1st param is String and only one arg, assume it is canvas filename fn.saveCanvas(cnv, args[0]); - } else { // ================================================= // OPTION 2: extension clarifies saveStrings vs. saveJSON @@ -1995,7 +2001,7 @@ function files(p5, fn){ let ext; if (options === undefined) { ext = filename.substring(filename.lastIndexOf('.') + 1, filename.length); - if(ext === filename) ext = 'csv'; + if (ext === filename) ext = 'csv'; } else { ext = options; } @@ -2128,6 +2134,6 @@ function files(p5, fn){ export default files; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { files(p5, p5.prototype); } diff --git a/src/io/index.js b/src/io/index.js index 474d035cd9..5476bf46fa 100644 --- a/src/io/index.js +++ b/src/io/index.js @@ -3,7 +3,7 @@ import table from './p5.Table.js'; import tableRow from './p5.TableRow.js'; import xml from './p5.XML.js'; -export default function(p5){ +export default function (p5) { p5.registerAddon(files); p5.registerAddon(table); p5.registerAddon(tableRow); diff --git a/src/io/p5.Table.js b/src/io/p5.Table.js index 47f5ccf273..b2187eff1f 100644 --- a/src/io/p5.Table.js +++ b/src/io/p5.Table.js @@ -11,10 +11,10 @@ class Table { this.rows = []; } - toString(separator=',') { + toString(separator = ',') { let rows = this.rows.map(row => row.arr); - if(!this.columns.some(column => column === null)){ + if (!this.columns.some(column => column === null)) { rows = [this.columns, ...rows]; } @@ -77,12 +77,12 @@ class Table { * describe('no image displayed'); * } */ - addRow (row) { - // make sure it is a valid TableRow + addRow(row) { + // make sure it is a valid TableRow const r = row || new p5.TableRow(); if (typeof r.arr === 'undefined' || typeof r.obj === 'undefined') { - //r = new p5.prototype.TableRow(r); + //r = new p5.prototype.TableRow(r); throw new Error(`invalid TableRow: ${r}`); } r.table = this; @@ -129,7 +129,7 @@ class Table { * describe('no image displayed'); * } */ - removeRow (id) { + removeRow(id) { this.rows[id].table = null; // remove reference to table const chunk = this.rows.splice(id + 1, this.rows.length); this.rows.pop(); @@ -170,7 +170,7 @@ class Table { * describe('no image displayed'); * } */ - getRow (r) { + getRow(r) { return this.rows[r]; } @@ -218,7 +218,7 @@ class Table { * describe('no image displayed'); * } */ - getRows () { + getRows() { return this.rows; } @@ -260,8 +260,8 @@ class Table { * describe('no image displayed'); * } */ - findRow (value, column) { - // try the Object + findRow(value, column) { + // try the Object if (typeof column === 'string') { for (let i = 0; i < this.rows.length; i++) { if (this.rows[i].obj[this.columns.indexOf(column)] === value) { @@ -269,7 +269,7 @@ class Table { } } } else { - // try the Array + // try the Array for (let j = 0; j < this.rows.length; j++) { if (this.rows[j].arr[column] === value) { return this.rows[j]; @@ -323,7 +323,7 @@ class Table { * describe('no image displayed'); * } */ - findRows (value, column) { + findRows(value, column) { const ret = []; if (typeof column === 'string') { for (let i = 0; i < this.rows.length; i++) { @@ -332,7 +332,7 @@ class Table { } } } else { - // try the Array + // try the Array for (let j = 0; j < this.rows.length; j++) { if (this.rows[j].arr[column] === value) { ret.push(this.rows[j]); @@ -380,7 +380,7 @@ class Table { * describe('no image displayed'); * } */ - matchRow (regexp, column) { + matchRow(regexp, column) { if (typeof column === 'number') { for (let j = 0; j < this.rows.length; j++) { if (this.rows[j].arr[column].match(regexp)) { @@ -454,7 +454,7 @@ class Table { * } * } */ - matchRows (regexp, column) { + matchRows(regexp, column) { const ret = []; if (typeof column === 'number') { for (let j = 0; j < this.rows.length; j++) { @@ -503,7 +503,7 @@ class Table { * describe('no image displayed'); * } */ - getColumn (value) { + getColumn(value) { const ret = []; if (typeof value === 'string') { for (let i = 0; i < this.rows.length; i++) { @@ -555,7 +555,7 @@ class Table { * describe('no image displayed'); * } */ - clearRows () { + clearRows() { delete this.rows; this.rows = []; } @@ -597,7 +597,7 @@ class Table { * describe('no image displayed'); * } */ - addColumn (title) { + addColumn(title) { const t = title || null; this.columns.push(t); } @@ -628,7 +628,7 @@ class Table { * text('There are ' + numOfColumn + ' columns in the table.', 100, 50); * } */ - getColumnCount () { + getColumnCount() { return this.columns.length; } @@ -658,7 +658,7 @@ class Table { * text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50); * } */ - getRowCount () { + getRowCount() { return this.rows.length; } @@ -698,7 +698,7 @@ class Table { * // 0 "Lion" "Mamal" * // 1 "Snake" "Reptile" */ - removeTokens (chars, column) { + removeTokens(chars, column) { const escape = s => s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); const charArray = []; for (let i = 0; i < chars.length; i++) { @@ -766,7 +766,7 @@ class Table { * // 0 "Lion" "Mamal" * // 1 "Snake" "Reptile" */ - trim (column) { + trim(column) { const regex = new RegExp(' ', 'g'); if (typeof column === 'undefined') { @@ -833,11 +833,11 @@ class Table { * describe('no image displayed'); * } */ - removeColumn (c) { + removeColumn(c) { let cString; let cNumber; if (typeof c === 'string') { - // find the position of c in the columns + // find the position of c in the columns cString = c; cNumber = this.columns.indexOf(c); } else { @@ -903,7 +903,7 @@ class Table { * describe('no image displayed'); * } */ - set (row, column, value) { + set(row, column, value) { this.rows[row].set(column, value); } @@ -944,7 +944,7 @@ class Table { * describe('no image displayed'); * } */ - setNum (row, column, value) { + setNum(row, column, value) { this.rows[row].setNum(column, value); } @@ -994,7 +994,7 @@ class Table { * describe('no image displayed'); * } */ - setString (row, column, value) { + setString(row, column, value) { this.rows[row].setString(column, value); } @@ -1035,8 +1035,8 @@ class Table { * describe('no image displayed'); * } */ - get (row, column) { - if(typeof column === 'string'){ + get(row, column) { + if (typeof column === 'string') { return this.rows[row].get(this.columns.indexOf(column)); } else { return this.rows[row].get(column); @@ -1076,7 +1076,7 @@ class Table { * describe('no image displayed'); * } */ - getNum (row, column) { + getNum(row, column) { return this.rows[row].getNum(column); } @@ -1130,7 +1130,7 @@ class Table { * describe('no image displayed'); * } */ - getString (row, column) { + getString(row, column) { return this.rows[row].getString(column); } @@ -1169,7 +1169,7 @@ class Table { * describe('no image displayed'); * } */ - getObject (headerColumn) { + getObject(headerColumn) { const tableObject = {}; let obj, cPos, index; @@ -1225,16 +1225,16 @@ class Table { * describe('no image displayed'); * } */ - getArray () { + getArray() { const tableArray = []; for (let i = 0; i < this.rows.length; i++) { tableArray.push(this.rows[i].arr); } return tableArray; } -}; +} -function table(p5, fn){ +function table(p5, fn) { /** * Table Options * Generic class for handling tabular data, typically from a @@ -1312,6 +1312,6 @@ function table(p5, fn){ export default table; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { table(p5, p5.prototype); } diff --git a/src/io/p5.TableRow.js b/src/io/p5.TableRow.js index 59e5d028ac..69da2eec30 100644 --- a/src/io/p5.TableRow.js +++ b/src/io/p5.TableRow.js @@ -4,7 +4,7 @@ */ class TableRow { - constructor(row=[]){ + constructor(row = []) { let arr = row; this.arr = arr; @@ -57,7 +57,7 @@ class TableRow { * } */ set(column, value) { - // if typeof column is string, use .obj + // if typeof column is string, use .obj if (typeof column === 'string') { const cPos = this.table.columns.indexOf(column); // index of columnID if (cPos >= 0) { @@ -67,7 +67,7 @@ class TableRow { throw new Error(`This table has no column named "${column}"`); } } else { - // if typeof column is number, use .arr + // if typeof column is number, use .arr if (column < this.table.columns.length) { this.arr[column] = value; const cTitle = this.table.columns[column]; @@ -325,9 +325,9 @@ class TableRow { return this.arr[column].toString(); } } -}; +} -function tableRow(p5, fn){ +function tableRow(p5, fn) { /** * A TableRow object represents a single row of data values, * stored in columns, from a table. @@ -346,6 +346,6 @@ function tableRow(p5, fn){ export default tableRow; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { tableRow(p5, p5.prototype); } diff --git a/src/io/p5.XML.js b/src/io/p5.XML.js index ba1cba3b9c..875ee66cd0 100644 --- a/src/io/p5.XML.js +++ b/src/io/p5.XML.js @@ -4,7 +4,7 @@ */ class XML { - constructor(DOM){ + constructor(DOM) { if (!DOM) { const xmlDoc = document.implementation.createDocument(null, 'doc'); this.DOM = xmlDoc.createElement('root'); @@ -470,7 +470,7 @@ class XML { if (node instanceof XML) { this.DOM.appendChild(node.DOM); } else { - // PEND + // PEND } } @@ -1142,7 +1142,7 @@ function elementsToP5XML(elements) { return arr; } -function xml(p5, fn){ +function xml(p5, fn) { /** * A class to describe an XML object. * @@ -1199,6 +1199,6 @@ function xml(p5, fn){ export default xml; export { XML }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { xml(p5, p5.prototype); } diff --git a/src/io/utilities.js b/src/io/utilities.js index ec82ad7179..7b9e895328 100644 --- a/src/io/utilities.js +++ b/src/io/utilities.js @@ -7,7 +7,7 @@ function downloadFile(data, fName, extension) { saveData = new Blob([data]); } - if(document){ + if (document) { const url = URL.createObjectURL(saveData); const link = document.createElement('a'); link.href = url; diff --git a/src/math/Matrices/MatrixNumjs.js b/src/math/Matrices/MatrixNumjs.js index 22ccf2e875..1e719ffea5 100644 --- a/src/math/Matrices/MatrixNumjs.js +++ b/src/math/Matrices/MatrixNumjs.js @@ -41,7 +41,7 @@ if (typeof Float32Array !== 'undefined') { * } */ // const matrixEngine = "numjs"; -export class MatrixNumjs extends MatrixInterface{ +export class MatrixNumjs extends MatrixInterface { constructor(...args) { // This is default behavior when object super(...args); @@ -125,7 +125,6 @@ export class MatrixNumjs extends MatrixInterface{ return this; } - /** * Gets a copy of the vector, returns a MatrixNumjs object. * @@ -958,4 +957,3 @@ export class MatrixNumjs extends MatrixInterface{ // 0.0,0.0,0.0,1.0 //]; } - diff --git a/src/math/calculation.js b/src/math/calculation.js index aaa3e61158..cd442a7fdf 100644 --- a/src/math/calculation.js +++ b/src/math/calculation.js @@ -4,7 +4,7 @@ * @for p5 */ -function calculation(p5, fn){ +function calculation(p5, fn) { /** * Calculates the absolute value of a number. * @@ -222,7 +222,7 @@ function calculation(p5, fn){ * circle(xc, 67, 9); * } */ - fn.constrain = function(n, low, high) { + fn.constrain = function (n, low, high) { // p5._validateParameters('constrain', arguments); return Math.max(Math.min(n, high), low); }; @@ -287,7 +287,7 @@ function calculation(p5, fn){ * @param {Number} z2 z-coordinate of the second point. * @return {Number} distance between the two points. */ - fn.dist = function(...args) { + fn.dist = function (...args) { // p5._validateParameters('dist', args); if (args.length === 4) { //2D @@ -295,7 +295,9 @@ function calculation(p5, fn){ } else if (args.length === 6) { //3D return Math.hypot( - args[3] - args[0], args[4] - args[1], args[5] - args[2] + args[3] - args[0], + args[4] - args[1], + args[5] - args[2] ); } }; @@ -604,7 +606,7 @@ function calculation(p5, fn){ * @param {Number} amt number. * @return {Number} lerped value. */ - fn.lerp = function(start, stop, amt) { + fn.lerp = function (start, stop, amt) { // p5._validateParameters('lerp', arguments); return amt * (stop - start) + start; }; @@ -748,7 +750,7 @@ function calculation(p5, fn){ * describe('A diagonal line is drawn from the top left of the canvas. The number 50 is written at the end of the line.'); * } */ - fn.mag = function(x, y) { + fn.mag = function (x, y) { // p5._validateParameters('mag', arguments); return Math.hypot(x, y); }; @@ -861,9 +863,10 @@ function calculation(p5, fn){ * @param {Boolean} [withinBounds] constrain the value to the newly mapped range. * @return {Number} remapped number. */ - fn.map = function(n, start1, stop1, start2, stop2, withinBounds) { + fn.map = function (n, start1, stop1, start2, stop2, withinBounds) { // p5._validateParameters('map', arguments); - const newval = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2; + const newval = + ((n - start1) / (stop1 - start1)) * (stop2 - start2) + start2; if (!withinBounds) { return newval; } @@ -977,7 +980,7 @@ function calculation(p5, fn){ * @param {Number[]} nums numbers to compare. * @return {Number} */ - fn.max = function(...args) { + fn.max = function (...args) { const findMax = arr => { let max = -Infinity; for (let x of arr) { @@ -1096,7 +1099,7 @@ function calculation(p5, fn){ * @param {Number[]} nums numbers to compare. * @return {Number} */ - fn.min = function(...args) { + fn.min = function (...args) { const findMin = arr => { let min = Infinity; for (let x of arr) { @@ -1146,7 +1149,7 @@ function calculation(p5, fn){ * background(redValue, 0, 0); * } */ - fn.norm = function(n, start, stop) { + fn.norm = function (n, start, stop) { // p5._validateParameters('norm', arguments); return this.map(n, start, stop, 0, 1); }; @@ -1333,7 +1336,7 @@ function calculation(p5, fn){ * @param {Number} [decimals] number of decimal places to round to, default is 0. * @return {Integer} rounded number. */ - fn.round = function(n, decimals) { + fn.round = function (n, decimals) { if (!decimals) { return Math.round(n); } @@ -1575,7 +1578,7 @@ function calculation(p5, fn){ * @param {Number} n number whose fractional part will be found. * @returns {Number} fractional part of n. */ - fn.fract = function(toConvert) { + fn.fract = function (toConvert) { // p5._validateParameters('fract', arguments); let sign = 0; let num = Number(toConvert); @@ -1599,6 +1602,6 @@ function calculation(p5, fn){ export default calculation; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { calculation(p5, p5.prototype); } diff --git a/src/math/index.js b/src/math/index.js index 8e08dd88bd..e68d095eae 100644 --- a/src/math/index.js +++ b/src/math/index.js @@ -6,7 +6,7 @@ import math from './math.js'; import vector from './p5.Vector.js'; import vectorValidation from './patch-vector.js'; -export default function(p5){ +export default function (p5) { p5.registerAddon(calculation); p5.registerAddon(noise); p5.registerAddon(random); diff --git a/src/math/math.js b/src/math/math.js index 71a6848e87..930394ddb1 100644 --- a/src/math/math.js +++ b/src/math/math.js @@ -97,7 +97,11 @@ function math(p5, fn) { this._boundFromRadians = this._fromRadians.bind(this); this._boundToRadians = this._toRadians.bind(this); } - return new p5.Vector(this._boundFromRadians, this._boundToRadians, ...args); + return new p5.Vector( + this._boundFromRadians, + this._boundToRadians, + ...args + ); } else { return new p5.Vector(...args); } diff --git a/src/math/noise.js b/src/math/noise.js index fcc26a42d8..335f8d5d5f 100644 --- a/src/math/noise.js +++ b/src/math/noise.js @@ -16,7 +16,7 @@ * @submodule Noise * @for p5 */ -function noise(p5, fn){ +function noise(p5, fn) { const PERLIN_YWRAPB = 4; const PERLIN_YWRAP = 1 << PERLIN_YWRAPB; const PERLIN_ZWRAPB = 8; @@ -264,7 +264,7 @@ function noise(p5, fn){ * @param {Number} [z] z-coordinate in noise space. * @return {Number} Perlin noise value at specified coordinates. */ - fn.noise = function(x, y = 0, z = 0) { + fn.noise = function (x, y = 0, z = 0) { if (perlin == null) { perlin = new Array(PERLIN_SIZE + 1); for (let i = 0; i < PERLIN_SIZE + 1; i++) { @@ -400,7 +400,7 @@ function noise(p5, fn){ * describe('Two gray cloudy patterns. The pattern on the right is cloudier than the pattern on the left.'); * } */ - fn.noiseDetail = function(lod, falloff=0.5) { + fn.noiseDetail = function (lod, falloff = 0.5) { if (lod > 0) { perlin_octaves = lod; } @@ -413,7 +413,7 @@ function noise(p5, fn){ * @private * Returns the current number of octaves used by noise(). */ - fn._getNoiseOctaves = function() { + fn._getNoiseOctaves = function () { return perlin_octaves; }; @@ -421,7 +421,7 @@ function noise(p5, fn){ * @private * Returns the current falloff factor used by noise(). */ - fn._getNoiseAmpFalloff = function() { + fn._getNoiseAmpFalloff = function () { return perlin_amp_falloff; }; @@ -463,7 +463,7 @@ function noise(p5, fn){ * line(x, 0, x, height); * } */ - fn.noiseSeed = function(seed) { + fn.noiseSeed = function (seed) { // Linear Congruential Generator // Variant of a Lehman Generator const lcg = (() => { @@ -505,6 +505,6 @@ function noise(p5, fn){ export default noise; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { noise(p5, p5.prototype); -} \ No newline at end of file +} diff --git a/src/math/p5.Vector.js b/src/math/p5.Vector.js index e5275ad7cc..f1443798d4 100644 --- a/src/math/p5.Vector.js +++ b/src/math/p5.Vector.js @@ -13,7 +13,9 @@ const prioritizeSmallerDimension = function (currentVectorDimension, args) { const resultDimension = Math.min(currentVectorDimension, args.length); if (Array.isArray(args) && currentVectorDimension !== args.length) { console.warn( - 'When working with two vectors of different sizes, the smaller dimension is used. In this operation, both vectors will be treated as ' + resultDimension + 'D vectors, and any additional values of the longer vector will be ignored.' + 'When working with two vectors of different sizes, the smaller dimension is used. In this operation, both vectors will be treated as ' + + resultDimension + + 'D vectors, and any additional values of the longer vector will be ignored.' ); } return resultDimension; @@ -23,12 +25,11 @@ const prioritizeSmallerDimension = function (currentVectorDimension, args) { * @private * In-place, shrinks an array to a dimension. */ -const shrinkToDimension = function(arr, dim) { +const shrinkToDimension = function (arr, dim) { while (arr.length > dim) { arr.pop(); } -} - +}; class Vector { /** @@ -70,11 +71,8 @@ class Vector { // This is how it comes in with createVector() // This check if the first argument is a function constructor(...args) { - if (args.length === 0) { - this._friendlyError( - 'Requires valid arguments.', 'p5.Vector' - ); + this._friendlyError('Requires valid arguments.', 'p5.Vector'); } if (typeof args[0] === 'function') { @@ -113,13 +111,12 @@ class Vector { // This will get overwritten when exported as part of p5. _friendlyError(_e) {} - /** * Gets how many dimensions the vector has. * * @returns {Number} The number of dimensions. Can be 1, 2, or 3. */ - get dimensions(){ + get dimensions() { return this.values.length; } @@ -653,13 +650,13 @@ class Vector { shrinkToDimension(this.values, minDimension); - if(Array.isArray(args)){ + if (Array.isArray(args)) { for (let i = 0; i < this.values.length; i++) { if (args[i] > 0) { this.values[i] = this.values[i] % args[i]; } } - } else if(args > 0) { + } else if (args > 0) { for (let i = 0; i < this.values.length; i++) { this.values[i] = this.values[i] % args; } @@ -982,7 +979,7 @@ class Vector { const minDimension = prioritizeSmallerDimension(this.dimensions, args); shrinkToDimension(this.values, minDimension); - if(Array.isArray(args)){ + if (Array.isArray(args)) { for (let i = 0; i < this.values.length; i++) { this.values[i] *= args[i]; } @@ -1178,7 +1175,7 @@ class Vector { if (Array.isArray(args)) { for (let i = 0; i < minDimension; i++) { - if ((typeof args[i] !== 'number' || args[i] === 0)) { + if (typeof args[i] !== 'number' || args[i] === 0) { if (!this.friendlyErrorsDisabled()) { console.warn( 'p5.Vector.prototype.div', @@ -1188,7 +1185,7 @@ class Vector { return this; } } - } else if(typeof args !== 'number' || args === 0) { + } else if (typeof args !== 'number' || args === 0) { if (!this.friendlyErrorsDisabled()) { console.warn( 'p5.Vector.prototype.div', @@ -1200,7 +1197,7 @@ class Vector { shrinkToDimension(this.values, minDimension); - if(Array.isArray(args)){ + if (Array.isArray(args)) { for (let i = 0; i < this.values.length; i++) { this.values[i] /= args[i]; } @@ -2022,13 +2019,15 @@ class Vector { * } */ setHeading(a) { - if (this.dimensions < 2 || ( - this._values instanceof Array && this._values.slice(2).some(v => v !== 0)) + if ( + this.dimensions < 2 || + (this._values instanceof Array && + this._values.slice(2).some(v => v !== 0)) ) { p5._friendlyError( 'p5.Vector.setHeading() only supports 2D vectors (z === 0). ' + - 'For 3D or higher-dimensional vectors, use rotate() or another ' + - 'appropriate method instead.', + 'For 3D or higher-dimensional vectors, use rotate() or another ' + + 'appropriate method instead.', 'p5.Vector.setHeading' ); return this; @@ -3660,7 +3659,7 @@ function vector(p5, fn) { p5.Vector = Vector; Vector.prototype._friendlyError = p5._friendlyError; - Vector.prototype.friendlyErrorsDisabled = function() { + Vector.prototype.friendlyErrorsDisabled = function () { return p5.disableFriendlyErrors; }; diff --git a/src/math/patch-vector.js b/src/math/patch-vector.js index d2121101f5..d79d56c9e0 100644 --- a/src/math/patch-vector.js +++ b/src/math/patch-vector.js @@ -4,15 +4,15 @@ import { Vector } from './p5.Vector.js'; * @private * @internal */ -export function _defaultEmptyVector(target){ - return function(...args){ - if(args.length === 0){ +export function _defaultEmptyVector(target) { + return function (...args) { + if (args.length === 0) { this.constructor._friendlyError( 'In 1.x, createVector() was a shortcut for createVector(0, 0, 0). In 2.x, p5.js has vectors of any dimension, so you must provide your desired number of zeros. Use createVector(0, 0) for a 2D vector and createVector(0, 0, 0) for a 3D vector.', 'p5.createVector' ); return target.call(this, 0, 0, 0); - }else{ + } else { if (Array.isArray(args[0])) { args = args[0]; } @@ -21,13 +21,12 @@ export function _defaultEmptyVector(target){ }; } - /** * @private * @internal */ -export function _validatedVectorOperation(expectsSoloNumberArgument){ - return function(target){ +export function _validatedVectorOperation(expectsSoloNumberArgument) { + return function (target) { return function (...args) { if (args.length === 0) { // No arguments? No action @@ -38,14 +37,14 @@ export function _validatedVectorOperation(expectsSoloNumberArgument){ } else if (Array.isArray(args[0])) { // First argument is an array? Great, keep it! args = args[0]; - } else if (args.length === 1){ + } else if (args.length === 1) { // Special case for a solo numeric arguments only applies sometimes if (expectsSoloNumberArgument) { args = args[0]; } } - if(Array.isArray(args)){ + if (Array.isArray(args)) { for (let i = 0; i < args.length; i++) { const v = args[i]; if (typeof v !== 'number' || !Number.isFinite(v)) { @@ -82,13 +81,26 @@ export function _validatedVectorOperation(expectsSoloNumberArgument){ * These ensure that the arguments are consistently formatted, and that * pre-conditions are met. */ -export default function vectorValidation(p5, fn, lifecycles){ - +export default function vectorValidation(p5, fn, lifecycles) { p5.registerDecorator('p5.prototype.createVector', _defaultEmptyVector); - p5.registerDecorator('p5.Vector.prototype.mult', _validatedVectorOperation(true)); - p5.registerDecorator('p5.Vector.prototype.rem', _validatedVectorOperation(true)); - p5.registerDecorator('p5.Vector.prototype.div', _validatedVectorOperation(true)); - p5.registerDecorator('p5.Vector.prototype.add', _validatedVectorOperation(false)); - p5.registerDecorator('p5.Vector.prototype.sub', _validatedVectorOperation(false)); - + p5.registerDecorator( + 'p5.Vector.prototype.mult', + _validatedVectorOperation(true) + ); + p5.registerDecorator( + 'p5.Vector.prototype.rem', + _validatedVectorOperation(true) + ); + p5.registerDecorator( + 'p5.Vector.prototype.div', + _validatedVectorOperation(true) + ); + p5.registerDecorator( + 'p5.Vector.prototype.add', + _validatedVectorOperation(false) + ); + p5.registerDecorator( + 'p5.Vector.prototype.sub', + _validatedVectorOperation(false) + ); } diff --git a/src/math/random.js b/src/math/random.js index 717c6bccc1..2f2cd93f14 100644 --- a/src/math/random.js +++ b/src/math/random.js @@ -4,7 +4,7 @@ * @for p5 */ -function random(p5, fn){ +function random(p5, fn) { // variables used for random number generators const randomStateProp = '_lcg_random_state'; // Set to values from http://en.wikipedia.org/wiki/Numerical_Recipes @@ -18,7 +18,7 @@ function random(p5, fn){ let y2 = 0; // Linear Congruential Generator that stores its state at instance[stateProperty] - fn._lcg = function(stateProperty) { + fn._lcg = function (stateProperty) { // define the recurrence relationship this[stateProperty] = (a * this[stateProperty] + c) % m; // return a float in [0, 1) @@ -26,7 +26,7 @@ function random(p5, fn){ return this[stateProperty] / m; }; - fn._lcgSetSeed = function(stateProperty, val) { + fn._lcgSetSeed = function (stateProperty, val) { // pick a random seed if val is undefined or null // the >>> 0 casts the seed to an unsigned 32-bit integer this[stateProperty] = (val == null ? Math.random() * m : val) >>> 0; @@ -72,7 +72,7 @@ function random(p5, fn){ * describe('A white circle appears at a random position. A black circle appears at (27.4, 25.8).'); * } */ - fn.randomSeed = function(seed) { + fn.randomSeed = function (seed) { this._lcgSetSeed(randomStateProp, seed); this._gaussian_previous = false; }; @@ -271,7 +271,7 @@ function random(p5, fn){ * @param {Array} choices array to choose from. * @return {*} random element from the array. */ - fn.random = function(min, max) { + fn.random = function (min, max) { // p5._validateParameters('random', arguments); let rand; @@ -357,7 +357,7 @@ function random(p5, fn){ * circle(x, y, 5); * } */ - fn.randomGaussian = function(mean, sd = 1) { + fn.randomGaussian = function (mean, sd = 1) { let y1, x1, x2, w; if (this._gaussian_previous) { y1 = y2; @@ -368,7 +368,7 @@ function random(p5, fn){ x2 = this.random(2) - 1; w = x1 * x1 + x2 * x2; } while (w >= 1); - w = Math.sqrt(-2 * Math.log(w) / w); + w = Math.sqrt((-2 * Math.log(w)) / w); y1 = x1 * w; y2 = x2 * w; this._gaussian_previous = true; @@ -381,6 +381,6 @@ function random(p5, fn){ export default random; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { random(p5, p5.prototype); } diff --git a/src/math/trigonometry.js b/src/math/trigonometry.js index fe389f5162..4355561996 100644 --- a/src/math/trigonometry.js +++ b/src/math/trigonometry.js @@ -6,7 +6,7 @@ import * as constants from '../core/constants'; -function trigonometry(p5, fn){ +function trigonometry(p5, fn) { /** * A `String` constant that's used to set the * angleMode(). @@ -42,7 +42,7 @@ function trigonometry(p5, fn){ * describe('The bottom half of a circle drawn on a gray background. The bottom-right quarter is red. The bottom-left quarter is blue.'); * } */ - const DEGREES = fn.DEGREES = 'degrees'; + const DEGREES = (fn.DEGREES = 'degrees'); /** * A `String` constant that's used to set the @@ -84,7 +84,7 @@ function trigonometry(p5, fn){ * describe('The bottom half of a circle drawn on a gray background. The bottom-right quarter is red. The bottom-left quarter is blue.'); * } */ - const RADIANS = fn.RADIANS = 'radians'; + const RADIANS = (fn.RADIANS = 'radians'); /* * all DEGREES/RADIANS conversion should be done in the p5 instance @@ -186,7 +186,7 @@ function trigonometry(p5, fn){ * @param {Number} value value whose arc cosine is to be returned. * @return {Number} arc cosine of the given value. */ - fn.acos = function(ratio) { + fn.acos = function (ratio) { return this._fromRadians(Math.acos(ratio)); }; @@ -284,7 +284,7 @@ function trigonometry(p5, fn){ * @param {Number} value value whose arc sine is to be returned. * @return {Number} arc sine of the given value. */ - fn.asin = function(ratio) { + fn.asin = function (ratio) { return this._fromRadians(Math.asin(ratio)); }; @@ -385,7 +385,7 @@ function trigonometry(p5, fn){ * @param {Number} value value whose arc tangent is to be returned. * @return {Number} arc tangent of the given value. */ - fn.atan = function(ratio) { + fn.atan = function (ratio) { return this._fromRadians(Math.atan(ratio)); }; @@ -455,7 +455,7 @@ function trigonometry(p5, fn){ * rect(-30, -5, 60, 10); * } */ - fn.atan2 = function(y, x) { + fn.atan2 = function (y, x) { return this._fromRadians(Math.atan2(y, x)); }; @@ -569,7 +569,7 @@ function trigonometry(p5, fn){ * @param {Number} angle the angle, in radians by default, or according to if angleMode() setting (RADIANS or DEGREES). * @return {Number} cosine of the angle. */ - fn.cos = function(angle) { + fn.cos = function (angle) { return Math.cos(this._toRadians(angle)); }; @@ -683,7 +683,7 @@ function trigonometry(p5, fn){ * @param {Number} angle the angle, in radians by default, or according to if angleMode() setting (RADIANS or DEGREES). * @return {Number} sine of the angle. */ - fn.sin = function(angle) { + fn.sin = function (angle) { return Math.sin(this._toRadians(angle)); }; @@ -761,7 +761,7 @@ function trigonometry(p5, fn){ * @param {Number} angle the angle, in radians by default, or according to if angleMode() setting (RADIANS or DEGREES). * @return {Number} tangent of the angle. */ - fn.tan = function(angle) { + fn.tan = function (angle) { return Math.tan(this._toRadians(angle)); }; @@ -1086,7 +1086,7 @@ function trigonometry(p5, fn){ * @method angleMode * @return {(RADIANS|DEGREES)} mode either RADIANS or DEGREES */ - fn.angleMode = function(mode) { + fn.angleMode = function (mode) { // p5._validateParameters('angleMode', arguments); if (typeof mode === 'undefined') { return this._angleMode; @@ -1094,11 +1094,11 @@ function trigonometry(p5, fn){ const prevMode = this._angleMode; // No change - if(mode === prevMode) return; + if (mode === prevMode) return; // Otherwise adjust pRotation according to new mode // This is necessary for acceleration events to work properly - if(mode === RADIANS) { + if (mode === RADIANS) { // Change pRotation to radians this.pRotationX = this.pRotationX * constants.DEG_TO_RAD; this.pRotationY = this.pRotationY * constants.DEG_TO_RAD; @@ -1122,7 +1122,7 @@ function trigonometry(p5, fn){ * @param {Number} angle * @returns {Number} */ - fn._toRadians = function(angle) { + fn._toRadians = function (angle) { // returns undefined if no argument if (typeof angle !== 'undefined' && this._angleMode === DEGREES) { return angle * constants.DEG_TO_RAD; @@ -1138,7 +1138,7 @@ function trigonometry(p5, fn){ * @param {Number} angle * @returns {Number} */ - fn._toDegrees = function(angle) { + fn._toDegrees = function (angle) { if (this._angleMode === RADIANS) { return angle * constants.RAD_TO_DEG; } @@ -1153,7 +1153,7 @@ function trigonometry(p5, fn){ * @param {Number} angle * @returns {Number} */ - fn._fromRadians = function(angle) { + fn._fromRadians = function (angle) { if (this._angleMode === DEGREES) { return angle * constants.RAD_TO_DEG; } @@ -1168,7 +1168,7 @@ function trigonometry(p5, fn){ * @param {Number} angle * @returns {Number} */ - fn._fromDegrees = function(angle) { + fn._fromDegrees = function (angle) { if (this._angleMode === RADIANS) { return angle * constants.DEG_TO_RAD; } @@ -1178,6 +1178,6 @@ function trigonometry(p5, fn){ export default trigonometry; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { trigonometry(p5, p5.prototype); } diff --git a/src/shape/2d_primitives.js b/src/shape/2d_primitives.js index 4fab185f69..8f9c2ebce9 100644 --- a/src/shape/2d_primitives.js +++ b/src/shape/2d_primitives.js @@ -7,7 +7,7 @@ import * as constants from '../core/constants'; import canvas from '../core/helpers'; -function primitives(p5, fn){ +function primitives(p5, fn) { /** * This function does 3 things: * @@ -30,13 +30,7 @@ function primitives(p5, fn){ * underlying ellipse. This is useful if you want to do something special * there (like rendering a whole ellipse instead). */ - fn._normalizeArcAngles = ( - start, - stop, - width, - height, - correctForScaling - ) => { + fn._normalizeArcAngles = (start, stop, width, height, correctForScaling) => { const epsilon = 0.00001; // Smallest visible angle on displays up to 4K. let separation; @@ -67,18 +61,19 @@ function primitives(p5, fn){ // Optionally adjust the angles to counter linear scaling. if (correctForScaling) { if (start <= constants.HALF_PI) { - start = Math.atan(width / height * Math.tan(start)); + start = Math.atan((width / height) * Math.tan(start)); } else if (start > constants.HALF_PI && start <= 3 * constants.HALF_PI) { - start = Math.atan(width / height * Math.tan(start)) + constants.PI; + start = Math.atan((width / height) * Math.tan(start)) + constants.PI; } else { - start = Math.atan(width / height * Math.tan(start)) + constants.TWO_PI; + start = + Math.atan((width / height) * Math.tan(start)) + constants.TWO_PI; } if (stop <= constants.HALF_PI) { - stop = Math.atan(width / height * Math.tan(stop)); + stop = Math.atan((width / height) * Math.tan(stop)); } else if (stop > constants.HALF_PI && stop <= 3 * constants.HALF_PI) { - stop = Math.atan(width / height * Math.tan(stop)) + constants.PI; + stop = Math.atan((width / height) * Math.tan(stop)) + constants.PI; } else { - stop = Math.atan(width / height * Math.tan(stop)) + constants.TWO_PI; + stop = Math.atan((width / height) * Math.tan(stop)) + constants.TWO_PI; } } @@ -273,7 +268,7 @@ function primitives(p5, fn){ * arc(50, 50, 80, 80, startAngle, endAngle, PIE); * } */ - fn.arc = function(x, y, w, h, start, stop, mode, detail) { + fn.arc = function (x, y, w, h, start, stop, mode, detail) { // this.validate("p5.arc", arguments); // p5._validateParameters('arc', arguments); @@ -294,8 +289,10 @@ function primitives(p5, fn){ stop = this._toRadians(stop); const vals = canvas.modeAdjust( - x, y, - w, h, + x, + y, + w, + h, this._renderer.states.ellipseMode ); const angles = this._normalizeArcAngles(start, stop, vals.w, vals.h, true); @@ -427,7 +424,7 @@ function primitives(p5, fn){ * perimeter of the ellipse. Default value is 25. Won't * draw a stroke for a detail of more than 50. */ - fn.ellipse = function(x, y, w, h, detailX) { + fn.ellipse = function (x, y, w, h, detailX) { // p5._validateParameters('ellipse', arguments); return this._renderEllipse(...arguments); }; @@ -469,15 +466,15 @@ function primitives(p5, fn){ * describe('A white circle with black outline in the middle of a gray canvas.'); * } */ - fn.circle = function(...args) { + fn.circle = function (...args) { // p5._validateParameters('circle', args); - const argss = args.slice( 0, 2); + const argss = args.slice(0, 2); argss.push(args[2], args[2]); return this._renderEllipse(...argss); }; // internal method for drawing ellipses (without parameter validation) - fn._renderEllipse = function(x, y, w, h, detailX) { + fn._renderEllipse = function (x, y, w, h, detailX) { // if the current stroke and fill settings wouldn't result in something // visible, exit immediately if ( @@ -493,8 +490,10 @@ function primitives(p5, fn){ } const vals = canvas.modeAdjust( - x, y, - w, h, + x, + y, + w, + h, this._renderer.states.ellipseMode ); this._renderer.ellipse([vals.x, vals.y, vals.w, vals.h, detailX]); @@ -630,7 +629,7 @@ function primitives(p5, fn){ * @param {Number} z2 the z-coordinate of the second point. * @chainable */ - fn.line = function(...args) { + fn.line = function (...args) { // p5._validateParameters('line', args); if (this._renderer.states.strokeColor) { @@ -731,7 +730,7 @@ function primitives(p5, fn){ * createCanvas(100, 100); * * background(200); - * + * * // Making point to 5 pixels. * strokeWeight(5); * @@ -807,7 +806,7 @@ function primitives(p5, fn){ * @param {p5.Vector} coordinateVector the coordinate vector. * @chainable */ - fn.point = function(...args) { + fn.point = function (...args) { // p5._validateParameters('point', args); if (this._renderer.states.strokeColor) { @@ -949,7 +948,7 @@ function primitives(p5, fn){ * @param {Integer} [detailY] * @chainable */ - fn.quad = function(...args) { + fn.quad = function (...args) { // p5._validateParameters('quad', args); if (this._renderer.states.strokeColor || this._renderer.states.fillColor) { @@ -957,11 +956,21 @@ function primitives(p5, fn){ // if 3D and we weren't passed 12 args, assume Z is 0 this._renderer.quad.call( this._renderer, - args[0], args[1], 0, - args[2], args[3], 0, - args[4], args[5], 0, - args[6], args[7], 0, - args[8], args[9]); + args[0], + args[1], + 0, + args[2], + args[3], + 0, + args[4], + args[5], + 0, + args[6], + args[7], + 0, + args[8], + args[9] + ); } else { this._renderer.quad(...args); //accessibile outputs @@ -1087,7 +1096,7 @@ function primitives(p5, fn){ * @param {Integer} [detailY] number of segments in the y-direction (for WebGL mode). * @chainable */ - fn.rect = function(...args) { + fn.rect = function (...args) { // p5._validateParameters('rect', args); return this._renderRect(...args); }; @@ -1185,14 +1194,14 @@ function primitives(p5, fn){ * square(-20, -30, 55); * } */ - fn.square = function(x, y, s, tl, tr, br, bl) { + fn.square = function (x, y, s, tl, tr, br, bl) { // p5._validateParameters('square', arguments); // duplicate width for height in case of square return this._renderRect.call(this, x, y, s, s, tl, tr, br, bl); }; // internal method to have renderer draw a rectangle - fn._renderRect = function() { + fn._renderRect = function () { if (this._renderer.states.strokeColor || this._renderer.states.fillColor) { // duplicate width for height in case only 3 arguments is provided if (arguments.length === 3) { @@ -1288,7 +1297,7 @@ function primitives(p5, fn){ * triangle(-20, 25, 8, -30, 36, 25); * } */ - fn.triangle = function(...args) { + fn.triangle = function (...args) { // p5._validateParameters('triangle', args); if (this._renderer.states.strokeColor || this._renderer.states.fillColor) { @@ -1306,6 +1315,6 @@ function primitives(p5, fn){ export default primitives; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { primitives(p5, p5.prototype); } diff --git a/src/shape/attributes.js b/src/shape/attributes.js index fcf3bfd04c..8a3553f878 100644 --- a/src/shape/attributes.js +++ b/src/shape/attributes.js @@ -6,7 +6,7 @@ import * as constants from '../core/constants'; -function attributes(p5, fn){ +function attributes(p5, fn) { /** * Changes where ellipses, circles, and arcs are drawn. * @@ -34,7 +34,7 @@ function attributes(p5, fn){ * way. JavaScript is a case-sensitive language. * * Calling `ellipseMode()` without an argument returns the current ellipseMode, either `CENTER`, `RADIUS`, `CORNER`, or `CORNERS`. - * + * * @method ellipseMode * @param {(CENTER|RADIUS|CORNER|CORNERS)} mode either CENTER, RADIUS, CORNER, or CORNERS * @chainable @@ -81,9 +81,10 @@ function attributes(p5, fn){ * @method ellipseMode * @return {(CENTER|RADIUS|CORNER|CORNERS)} the current ellipseMode. */ - fn.ellipseMode = function(m) { + fn.ellipseMode = function (m) { // p5._validateParameters('ellipseMode', arguments); - if (typeof m === 'undefined') { // getter + if (typeof m === 'undefined') { + // getter return this._renderer?.states.ellipseMode; } if ( @@ -159,7 +160,7 @@ function attributes(p5, fn){ * describe('A pixelated white circle on a gray background.'); * } */ - fn.noSmooth = function() { + fn.noSmooth = function () { if (!this._renderer.isP3D) { if ('imageSmoothingEnabled' in this.drawingContext) { this.drawingContext.imageSmoothingEnabled = false; @@ -269,9 +270,10 @@ function attributes(p5, fn){ * @method rectMode * @return {(CENTER|RADIUS|CORNER|CORNERS)} the current rectMode. */ - fn.rectMode = function(m) { + fn.rectMode = function (m) { // p5._validateParameters('rectMode', arguments); - if (typeof m === 'undefined') { // getter + if (typeof m === 'undefined') { + // getter return this._renderer?.states.rectMode; } if ( @@ -347,7 +349,7 @@ function attributes(p5, fn){ * describe('A pixelated white circle on a gray background.'); * } */ - fn.smooth = function() { + fn.smooth = function () { if (!this._renderer.isP3D) { if ('imageSmoothingEnabled' in this.drawingContext) { this.drawingContext.imageSmoothingEnabled = true; @@ -396,9 +398,10 @@ function attributes(p5, fn){ * ); * } */ - fn.strokeCap = function(cap) { + fn.strokeCap = function (cap) { // p5._validateParameters('strokeCap', arguments); - if (typeof cap === 'undefined') { // getter + if (typeof cap === 'undefined') { + // getter return this._renderer.strokeCap(); } if ( @@ -494,9 +497,10 @@ function attributes(p5, fn){ * @method strokeJoin * @return {(MITER|BEVEL|ROUND)} the current stroke join style. */ - fn.strokeJoin = function(join) { + fn.strokeJoin = function (join) { // p5._validateParameters('strokeJoin', arguments); - if (typeof join === 'undefined') { // getter + if (typeof join === 'undefined') { + // getter return this._renderer.strokeJoin(); } if ( @@ -516,7 +520,7 @@ function attributes(p5, fn){ * Note: In 2D mode, `strokeWeight()` is affected by transformations, * especially calls to scale(). It isn't affected by * transformations in WebGL and WebGPU modes. - * + * * Calling `strokeWeight()` without an argument returns the current stroke weight as a number. * * @method strokeWeight @@ -564,7 +568,7 @@ function attributes(p5, fn){ * @method strokeWeight * @return {Number} the current stroke weight. */ - fn.strokeWeight = function(w) { + fn.strokeWeight = function (w) { // p5._validateParameters('strokeWeight', arguments); return this._renderer.strokeWeight(w); }; @@ -572,6 +576,6 @@ function attributes(p5, fn){ export default attributes; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { attributes(p5, p5.prototype); } diff --git a/src/shape/curves.js b/src/shape/curves.js index 7e2b49a2a1..842d13ecdf 100644 --- a/src/shape/curves.js +++ b/src/shape/curves.js @@ -4,7 +4,7 @@ * @for p5 */ -function curves(p5, fn){ +function curves(p5, fn) { /** * Draws a Bézier curve. * @@ -185,7 +185,7 @@ function curves(p5, fn){ * @param {Number} z4 z-coordinate of the second anchor point. * @chainable */ - fn.bezier = function(...args) { + fn.bezier = function (...args) { // p5._validateParameters('bezier', args); // if the current stroke and fill settings wouldn't result in something @@ -309,7 +309,7 @@ function curves(p5, fn){ * circle(x, y, 5); * } */ - fn.bezierPoint = function(a, b, c, d, t) { + fn.bezierPoint = function (a, b, c, d, t) { // p5._validateParameters('bezierPoint', arguments); const adjustedT = 1 - t; @@ -423,7 +423,7 @@ function curves(p5, fn){ * ); * } */ - fn.bezierTangent = function(a, b, c, d, t) { + fn.bezierTangent = function (a, b, c, d, t) { // p5._validateParameters('bezierTangent', arguments); const adjustedT = 1 - t; @@ -677,7 +677,7 @@ function curves(p5, fn){ * @param {Number} z4 z-coordinate of point p3. * @chainable */ - fn.spline = function(...args) { + fn.spline = function (...args) { if ( !this._renderer.states.strokeColor && !this._renderer.states.fillColor @@ -853,14 +853,14 @@ function curves(p5, fn){ * text('t = ' + nf(t, 1, 2) + ' (p1→p2)', 8, 16); * } */ - fn.splinePoint = function(a, b, c, d, t) { + fn.splinePoint = function (a, b, c, d, t) { const s = this._renderer.states.splineProperties.tightness, t3 = t * t * t, t2 = t * t, - f1 = (s - 1) / 2 * t3 + (1 - s) * t2 + (s - 1) / 2 * t, - f2 = (s + 3) / 2 * t3 + (-5 - s) / 2 * t2 + 1.0, - f3 = (-3 - s) / 2 * t3 + (s + 2) * t2 + (1 - s) / 2 * t, - f4 = (1 - s) / 2 * t3 + (s - 1) / 2 * t2; + f1 = ((s - 1) / 2) * t3 + (1 - s) * t2 + ((s - 1) / 2) * t, + f2 = ((s + 3) / 2) * t3 + ((-5 - s) / 2) * t2 + 1.0, + f3 = ((-3 - s) / 2) * t3 + (s + 2) * t2 + ((1 - s) / 2) * t, + f4 = ((1 - s) / 2) * t3 + ((s - 1) / 2) * t2; return a * f1 + b * f2 + c * f3 + d * f4; }; @@ -1001,20 +1001,20 @@ function curves(p5, fn){ * ); * } */ - fn.splineTangent = function(a, b, c, d, t) { + fn.splineTangent = function (a, b, c, d, t) { const s = this._renderer.states.splineProperties.tightness, tt3 = t * t * 3, t2 = t * 2, - f1 = (s - 1) / 2 * tt3 + (1 - s) * t2 + (s - 1) / 2, - f2 = (s + 3) / 2 * tt3 + (-5 - s) / 2 * t2, - f3 = (-3 - s) / 2 * tt3 + (s + 2) * t2 + (1 - s) / 2, - f4 = (1 - s) / 2 * tt3 + (s - 1) / 2 * t2; + f1 = ((s - 1) / 2) * tt3 + (1 - s) * t2 + (s - 1) / 2, + f2 = ((s + 3) / 2) * tt3 + ((-5 - s) / 2) * t2, + f3 = ((-3 - s) / 2) * tt3 + (s + 2) * t2 + (1 - s) / 2, + f4 = ((1 - s) / 2) * tt3 + ((s - 1) / 2) * t2; return a * f1 + b * f2 + c * f3 + d * f4; }; } export default curves; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { curves(p5, p5.prototype); } diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index 305cf2473b..5535656b45 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -13,7 +13,7 @@ import * as constants from '../core/constants'; function polylineLength(vertices) { let length = 0; for (let i = 1; i < vertices.length; i++) { - length += vertices[i-1].position.dist(vertices[i].position); + length += vertices[i - 1].position.dist(vertices[i].position); } return length; } @@ -49,12 +49,13 @@ class ShapePrimitive { constructor(...vertices) { if (this.constructor === ShapePrimitive) { - throw new Error('ShapePrimitive is an abstract class: it cannot be instantiated.'); + throw new Error( + 'ShapePrimitive is an abstract class: it cannot be instantiated.' + ); } if (vertices.length > 0) { this.vertices = vertices; - } - else { + } else { throw new Error('At least one vertex must be passed to the constructor.'); } } @@ -94,15 +95,14 @@ class ShapePrimitive { // last primitive in shape let lastPrimitive = shape.at(-1, -1); let hasSameType = lastPrimitive instanceof this.constructor; - let spareCapacity = lastPrimitive.vertexCapacity - - lastPrimitive.vertexCount; + let spareCapacity = + lastPrimitive.vertexCapacity - lastPrimitive.vertexCount; // this primitive let pushableVertices; let remainingVertices; if (hasSameType && spareCapacity > 0) { - pushableVertices = this.vertices.splice(0, spareCapacity); remainingVertices = this.vertices; lastPrimitive.vertices.push(...pushableVertices); @@ -110,8 +110,7 @@ class ShapePrimitive { if (remainingVertices.length > 0) { lastContour.primitives.push(this); } - } - else { + } else { lastContour.primitives.push(this); } } @@ -131,9 +130,9 @@ class ShapePrimitive { } get _nextPrimitive() { - return this._belongsToShape ? - this._shape.at(this._contoursIndex, this._primitivesIndex + 1) : - null; + return this._belongsToShape + ? this._shape.at(this._contoursIndex, this._primitivesIndex + 1) + : null; } get _belongsToShape() { @@ -194,16 +193,18 @@ class Segment extends ShapePrimitive { constructor(...vertices) { super(...vertices); if (this.constructor === Segment) { - throw new Error('Segment is an abstract class: it cannot be instantiated.'); + throw new Error( + 'Segment is an abstract class: it cannot be instantiated.' + ); } } // segments in a shape always have a predecessor // (either an anchor or another segment) get _previousPrimitive() { - return this._belongsToShape ? - this._shape.at(this._contoursIndex, this._primitivesIndex - 1) : - null; + return this._belongsToShape + ? this._shape.at(this._contoursIndex, this._primitivesIndex - 1) + : null; } getStartVertex() { @@ -305,9 +306,7 @@ class SplineSegment extends Segment { // the first vertex is in an anchor get _firstInterpolatedVertex() { if (this._splineProperties.ends === constants.EXCLUDE) { - return this._comesAfterSegment ? - this.vertices[1] : - this.vertices[0]; + return this._comesAfterSegment ? this.vertices[1] : this.vertices[0]; } else { return this.getStartVertex(); } @@ -318,8 +317,7 @@ class SplineSegment extends Segment { let interpolatedStartPosition = this._firstInterpolatedVertex.position; let predecessorEndPosition = this.getStartVertex().position; return predecessorEndPosition.equals(interpolatedStartPosition); - } - else { + } else { return false; } } @@ -341,7 +339,8 @@ class SplineSegment extends Segment { second spline vertex at (${array1}) expected to be at (${array2}).`; - if (verticesPushed && + if ( + verticesPushed && // Only check once the first interpolated vertex has been added lastPrimitive.vertices.length === 2 && lastPrimitive._comesAfterSegment && @@ -350,9 +349,7 @@ class SplineSegment extends Segment { let interpolatedStart = lastPrimitive._firstInterpolatedVertex.position; let predecessorEnd = lastPrimitive.getStartVertex().position; - console.warn( - message(interpolatedStart.array(), predecessorEnd.array()) - ); + console.warn(message(interpolatedStart.array(), predecessorEnd.array())); } // Note: Could add a warning in an else-if case for when this spline segment @@ -494,15 +491,33 @@ class ArcPrimitive extends ShapePrimitive { this.#mode = mode; } - get x() { return this.#x; } - get y() { return this.#y; } - get w() { return this.#w; } - get h() { return this.#h; } - get start() { return this.#start; } - get stop() { return this.#stop; } - get mode() { return this.#mode; } - get startVertex() { return this.vertices[0]; } - get endVertex() { return this.vertices[1]; } + get x() { + return this.#x; + } + get y() { + return this.#y; + } + get w() { + return this.#w; + } + get h() { + return this.#h; + } + get start() { + return this.#start; + } + get stop() { + return this.#stop; + } + get mode() { + return this.#mode; + } + get startVertex() { + return this.vertices[0]; + } + get endVertex() { + return this.vertices[1]; + } get vertexCapacity() { return this.#vertexCapacity; @@ -521,7 +536,6 @@ class EllipsePrimitive extends ShapePrimitive { #vertexCapacity = 1; constructor(centerVertex, x, y, w, h) { - super(centerVertex); this.#x = x; this.#y = y; @@ -529,10 +543,18 @@ class EllipsePrimitive extends ShapePrimitive { this.#h = h; } - get x() { return this.#x; } - get y() { return this.#y; } - get w() { return this.#w; } - get h() { return this.#h; } + get x() { + return this.#x; + } + get y() { + return this.#y; + } + get w() { + return this.#w; + } + get h() { + return this.#h; + } get vertexCapacity() { return this.#vertexCapacity; @@ -566,14 +588,30 @@ class RectPrimitive extends ShapePrimitive { this.#bl = bl; } - get x() { return this.#x; } - get y() { return this.#y; } - get w() { return this.#w; } - get h() { return this.#h; } - get tl() { return this.#tl; } - get tr() { return this.#tr; } - get br() { return this.#br; } - get bl() { return this.#bl; } + get x() { + return this.#x; + } + get y() { + return this.#y; + } + get w() { + return this.#w; + } + get h() { + return this.#h; + } + get tl() { + return this.#tl; + } + get tr() { + return this.#tr; + } + get br() { + return this.#br; + } + get bl() { + return this.#bl; + } get vertexCapacity() { return this.#vertexCapacity; @@ -653,23 +691,62 @@ class PrimitiveShapeCreators { */ // vertex - creators.set(`vertex-${constants.EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); - creators.set(`vertex-${constants.PATH}`, (...vertices) => new LineSegment(...vertices)); - creators.set(`vertex-${constants.POINTS}`, (...vertices) => new Point(...vertices)); - creators.set(`vertex-${constants.LINES}`, (...vertices) => new Line(...vertices)); - creators.set(`vertex-${constants.TRIANGLES}`, (...vertices) => new Triangle(...vertices)); - creators.set(`vertex-${constants.QUADS}`, (...vertices) => new Quad(...vertices)); - creators.set(`vertex-${constants.TRIANGLE_FAN}`, (...vertices) => new TriangleFan(...vertices)); - creators.set(`vertex-${constants.TRIANGLE_STRIP}`, (...vertices) => new TriangleStrip(...vertices)); - creators.set(`vertex-${constants.QUAD_STRIP}`, (...vertices) => new QuadStrip(...vertices)); + creators.set( + `vertex-${constants.EMPTY_PATH}`, + (...vertices) => new Anchor(...vertices) + ); + creators.set( + `vertex-${constants.PATH}`, + (...vertices) => new LineSegment(...vertices) + ); + creators.set( + `vertex-${constants.POINTS}`, + (...vertices) => new Point(...vertices) + ); + creators.set( + `vertex-${constants.LINES}`, + (...vertices) => new Line(...vertices) + ); + creators.set( + `vertex-${constants.TRIANGLES}`, + (...vertices) => new Triangle(...vertices) + ); + creators.set( + `vertex-${constants.QUADS}`, + (...vertices) => new Quad(...vertices) + ); + creators.set( + `vertex-${constants.TRIANGLE_FAN}`, + (...vertices) => new TriangleFan(...vertices) + ); + creators.set( + `vertex-${constants.TRIANGLE_STRIP}`, + (...vertices) => new TriangleStrip(...vertices) + ); + creators.set( + `vertex-${constants.QUAD_STRIP}`, + (...vertices) => new QuadStrip(...vertices) + ); // bezierVertex (constructors all take order and vertices so they can be called in a uniform way) - creators.set(`bezierVertex-${constants.EMPTY_PATH}`, (order, ...vertices) => new Anchor(...vertices)); - creators.set(`bezierVertex-${constants.PATH}`, (order, ...vertices) => new BezierSegment(order, ...vertices)); + creators.set( + `bezierVertex-${constants.EMPTY_PATH}`, + (order, ...vertices) => new Anchor(...vertices) + ); + creators.set( + `bezierVertex-${constants.PATH}`, + (order, ...vertices) => new BezierSegment(order, ...vertices) + ); // splineVertex - creators.set(`splineVertex-${constants.EMPTY_PATH}`, (...vertices) => new Anchor(...vertices)); - creators.set(`splineVertex-${constants.PATH}`, (...vertices) => new SplineSegment(...vertices)); + creators.set( + `splineVertex-${constants.EMPTY_PATH}`, + (...vertices) => new Anchor(...vertices) + ); + creators.set( + `splineVertex-${constants.PATH}`, + (...vertices) => new SplineSegment(...vertices) + ); this.creators = creators; } @@ -719,7 +796,7 @@ class Shape { for (const key in this.#vertexProperties) { if (key !== 'position' && key !== 'textureCoordinates') { - this[key] = function(value) { + this[key] = function (value) { this.#vertexProperties[key] = value; }; } @@ -729,7 +806,8 @@ class Shape { serializeToArray(val) { if (val === null || val === undefined) { return []; - } if (val instanceof Number) { + } + if (val instanceof Number) { return [val]; } else if (val instanceof Array) { return val; @@ -902,7 +980,7 @@ class Shape { contour = this.contours.at(contoursIndex); - switch(arguments.length) { + switch (arguments.length) { case 1: return contour; case 2: @@ -977,12 +1055,13 @@ class Shape { #createPrimitiveShape(vertexKind, shapeKind, ...vertices) { let primitiveShapeCreator = this.#primitiveShapeCreators.get( - vertexKind, shapeKind + vertexKind, + shapeKind ); - return vertexKind === 'bezierVertex' ? - primitiveShapeCreator(this.#bezierOrder, ...vertices) : - primitiveShapeCreator(...vertices); + return vertexKind === 'bezierVertex' + ? primitiveShapeCreator(this.#bezierOrder, ...vertices) + : primitiveShapeCreator(...vertices); } /* @@ -1023,11 +1102,10 @@ class Shape { this.#generalVertex('arcVertex', position, textureCoordinates); } - - arcPrimitive(x,y,w,h,start,stop,mode){ + arcPrimitive(x, y, w, h, start, stop, mode) { this.beginShape(); - const centerX = x+w/2; - const centerY = y+h/2; + const centerX = x + w / 2; + const centerY = y + h / 2; const radiusX = w / 2; const radiusY = h / 2; @@ -1048,7 +1126,10 @@ class Shape { const primitive = new ArcPrimitive( startVertex, endVertex, - x, y, w, h, + x, + y, + w, + h, start, stop, mode @@ -1056,11 +1137,10 @@ class Shape { primitive.addToShape(this); this.endShape(); return this; - } - ellipsePrimitive(x,y,w,h){ - const centerVertex = this.#createVertex(new Vector(x+w/2,y+h/2)); + ellipsePrimitive(x, y, w, h) { + const centerVertex = this.#createVertex(new Vector(x + w / 2, y + h / 2)); const primitive = new EllipsePrimitive(centerVertex, x, y, w, h); return primitive.addToShape(this); @@ -1068,7 +1148,17 @@ class Shape { rectPrimitive(x, y, w, h, tl, tr, br, bl) { const startVertex = this.#createVertex(new Vector(x, y)); - const primitive = new RectPrimitive(startVertex, x, y, w, h, tl, tr, br, bl); + const primitive = new RectPrimitive( + startVertex, + x, + y, + w, + h, + tl, + tr, + br, + bl + ); return primitive.addToShape(this); } @@ -1137,11 +1227,9 @@ class Shape { if (['position', 'textureCoordinates'].includes(key)) continue; this.#vertexProperties[key] = anchorVertex[key]; } - this.vertex( - anchorVertex.position, - anchorVertex.textureCoordinates, - { isClosing: true } - ); + this.vertex(anchorVertex.position, anchorVertex.textureCoordinates, { + isClosing: true + }); this.#vertexProperties = prevVertexProperties; this.contours.push(...rest); } @@ -1181,7 +1269,9 @@ class Shape { class PrimitiveVisitor { constructor() { if (this.constructor === PrimitiveVisitor) { - throw new Error('PrimitiveVisitor is an abstract class: it cannot be instantiated.'); + throw new Error( + 'PrimitiveVisitor is an abstract class: it cannot be instantiated.' + ); } } // path primitives @@ -1298,13 +1388,15 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { this.path.moveTo(startVertex.position.x, startVertex.position.y); } - const arrayVertices = splineSegment.getControlPoints().map( - v => shape.vertexToArray(v) - ); - let bezierArrays = shape.catmullRomToBezier( - arrayVertices, - splineSegment._splineProperties.tightness - ).map(arr => arr.map(vertArr => shape.arrayToVertex(vertArr))); + const arrayVertices = splineSegment + .getControlPoints() + .map(v => shape.vertexToArray(v)); + let bezierArrays = shape + .catmullRomToBezier( + arrayVertices, + splineSegment._splineProperties.tightness + ) + .map(arr => arr.map(vertArr => shape.arrayToVertex(vertArr))); for (const array of bezierArrays) { const points = array.flatMap(vert => [vert.position.x, vert.position.y]); this.path.bezierCurveTo(...points); @@ -1368,10 +1460,10 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { const startY = centerY + radiusY * Math.sin(arc.start); const delta = arc.stop - arc.start; - const isFullCircle = Math.abs(delta % (2 * Math.PI)) < 0.00001 && - Math.abs(delta) > 0.00001; + const isFullCircle = + Math.abs(delta % (2 * Math.PI)) < 0.00001 && Math.abs(delta) > 0.00001; - const createPieSlice = ! ( + const createPieSlice = !( arc.mode === constants.CHORD || arc.mode === constants.OPEN || isFullCircle @@ -1381,16 +1473,30 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { if (!this.strokePath) this.strokePath = new Path2D(this.path); this.fillPath.moveTo(startX, startY); - this.fillPath.ellipse(centerX, centerY, radiusX, radiusY, - 0, arc.start, arc.stop); + this.fillPath.ellipse( + centerX, + centerY, + radiusX, + radiusY, + 0, + arc.start, + arc.stop + ); if (createPieSlice) { this.fillPath.lineTo(centerX, centerY); } this.fillPath.closePath(); this.strokePath.moveTo(startX, startY); - this.strokePath.ellipse(centerX, centerY, radiusX, radiusY, - 0, arc.start, arc.stop); + this.strokePath.ellipse( + centerX, + centerY, + radiusX, + radiusY, + 0, + arc.start, + arc.stop + ); if (arc.mode === constants.PIE && createPieSlice) { this.strokePath.lineTo(centerX, centerY); } @@ -1400,8 +1506,15 @@ class PrimitiveToPath2DConverter extends PrimitiveVisitor { // Still maintain base path just in case this.path.moveTo(startX, startY); - this.path.ellipse(centerX, centerY, radiusX, radiusY, - 0, arc.start, arc.stop); + this.path.ellipse( + centerX, + centerY, + radiusX, + radiusY, + 0, + arc.start, + arc.stop + ); } visitEllipsePrimitive(ellipse) { const centerX = ellipse.x + ellipse.w / 2; @@ -1538,9 +1651,9 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { const shape = splineSegment._shape; const contour = this.lastContour(); - const arrayVertices = splineSegment.getControlPoints().map( - v => shape.vertexToArray(v) - ); + const arrayVertices = splineSegment + .getControlPoints() + .map(v => shape.vertexToArray(v)); let bezierArrays = shape.catmullRomToBezier( arrayVertices, splineSegment._splineProperties.tightness @@ -1554,7 +1667,7 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { 1, Math.ceil( polylineLength(bezierControls.map(v => shape.arrayToVertex(v))) * - this.curveDetail + this.curveDetail ) ); for (let i = 0; i < numPoints; i++) { @@ -1608,17 +1721,17 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { const numPoints = Math.max(3, Math.ceil(this.curveDetail * arcLength)); const verts = []; const interpolateVertexProps = (v1, v2, t) => { - const props = {}; - for (const [key, value] of Object.entries(v1)) { - if (key === 'position') continue; - if (typeof value === 'number' && typeof v2[key] === 'number') { - props[key] = value * (1 - t) + v2[key] * t; - } else { - props[key] = value; + const props = {}; + for (const [key, value] of Object.entries(v1)) { + if (key === 'position') continue; + if (typeof value === 'number' && typeof v2[key] === 'number') { + props[key] = value * (1 - t) + v2[key] * t; + } else { + props[key] = value; + } } - } - return props; - }; + return props; + }; if (arc.mode === constants.PIE) { const centerProps = interpolateVertexProps(startVertex, endVertex, 0.5); centerProps.position = new Vector(centerX, centerY); @@ -1714,11 +1827,13 @@ class PrimitiveToVerticesConverter extends PrimitiveVisitor { if (absH < 2 * bl) bl = hh; const addCornerArc = (cx, cy, rx, ry, startAngle, endAngle) => { - const perimeter = Math.PI / 2 * (rx + ry) / 2; + const perimeter = ((Math.PI / 2) * (rx + ry)) / 2; const numPoints = Math.max(1, Math.ceil(this.curveDetail * perimeter)); for (let i = 0; i <= numPoints; i++) { const angle = startAngle + (endAngle - startAngle) * (i / numPoints); - verts.push(getVertexProps(cx + rx * Math.cos(angle), cy + ry * Math.sin(angle))); + verts.push( + getVertexProps(cx + rx * Math.cos(angle), cy + ry * Math.sin(angle)) + ); } }; @@ -2040,7 +2155,6 @@ function customShapes(p5, fn) { // ---- FUNCTIONS ---- - /** * Influences the shape of the Bézier curve segment in a custom shape. * By default, this is 3; the other possible parameter is 2. This @@ -2103,12 +2217,10 @@ function customShapes(p5, fn) { * @method bezierOrder * @returns {Number} The current Bézier order. */ - fn.bezierOrder = function(order) { + fn.bezierOrder = function (order) { return this._renderer.bezierOrder(order); }; - - /** * Connects points with a smooth curve (a spline). * @@ -2418,8 +2530,12 @@ function customShapes(p5, fn) { * @param {Number} [u=0] * @param {Number} [v=0] */ - fn.splineVertex = function(...args) { - let x = 0, y = 0, z = 0, u = 0, v = 0; + fn.splineVertex = function (...args) { + let x = 0, + y = 0, + z = 0, + u = 0, + v = 0; if (args.length === 2) { [x, y] = args; } else if (args.length === 4) { @@ -2703,7 +2819,7 @@ function customShapes(p5, fn) { * @param {String} property * @returns The current value for the given property. */ - fn.splineProperty = function(property, value) { + fn.splineProperty = function (property, value) { return this._renderer.splineProperty(property, value); }; @@ -2809,7 +2925,7 @@ function customShapes(p5, fn) { * @method splineProperties * @return {Object} */ - fn.splineProperties = function(values) { + fn.splineProperties = function (values) { return this._renderer.splineProperties(values); }; @@ -3017,7 +3133,7 @@ function customShapes(p5, fn) { * @param {Number} [u=0] u-coordinate of the vertex's texture. * @param {Number} [v=0] v-coordinate of the vertex's texture. */ - fn.vertex = function(x, y) { + fn.vertex = function (x, y) { let z, u, v; // default to (x, y) mode: all other arguments assumed to be 0. @@ -3131,7 +3247,7 @@ function customShapes(p5, fn) { * endShape(CLOSE); * } */ - fn.beginContour = function(kind) { + fn.beginContour = function (kind) { this._renderer.beginContour(kind); }; @@ -3230,7 +3346,7 @@ function customShapes(p5, fn) { * endShape(CLOSE); * } */ - fn.endContour = function(mode = constants.OPEN) { + fn.endContour = function (mode = constants.OPEN) { this._renderer.endContour(mode); }; } diff --git a/src/shape/index.js b/src/shape/index.js index dbc520ff83..acd1f63c39 100644 --- a/src/shape/index.js +++ b/src/shape/index.js @@ -4,10 +4,10 @@ import curves from './curves.js'; import vertex from './vertex.js'; import customShapes from './custom_shapes.js'; -export default function(p5){ +export default function (p5) { p5.registerAddon(primitives); p5.registerAddon(attributes); p5.registerAddon(curves); p5.registerAddon(vertex); p5.registerAddon(customShapes); -} \ No newline at end of file +} diff --git a/src/shape/vertex.js b/src/shape/vertex.js index 0f41a6c043..20f603a67d 100644 --- a/src/shape/vertex.js +++ b/src/shape/vertex.js @@ -4,7 +4,7 @@ * @for p5 */ -function vertex(p5, fn){ +function vertex(p5, fn) { /** * Begins adding vertices to a custom shape. * @@ -355,7 +355,7 @@ function vertex(p5, fn){ * endShape(CLOSE); * } */ - fn.beginShape = function(kind) { + fn.beginShape = function (kind) { // p5._validateParameters('beginShape', arguments); this._renderer.beginShape(...arguments); }; @@ -625,7 +625,7 @@ function vertex(p5, fn){ * @param {Number} [u] * @param {Number} [v] */ - fn.bezierVertex = function(...args) { + fn.bezierVertex = function (...args) { this._renderer.bezierVertex(...args); }; @@ -833,7 +833,7 @@ function vertex(p5, fn){ * describe('A row of four squares. Their colors transition from purple on the left to red on the right'); * } */ - fn.endShape = function(mode, count = 1) { + fn.endShape = function (mode, count = 1) { // p5._validateParameters('endShape', arguments); if (count < 1) { console.log('🌸 p5.js says: You can not have less than one instance'); @@ -1014,7 +1014,7 @@ function vertex(p5, fn){ * @param {Number} z z-component of the vertex normal. * @chainable */ - fn.normal = function(x, y, z) { + fn.normal = function (x, y, z) { this._assert3d('normal'); // p5._validateParameters('normal', arguments); this._renderer.normal(...arguments); @@ -1183,7 +1183,7 @@ function vertex(p5, fn){ * endShape(); * } */ - fn.vertexProperty = function(attributeName, data){ + fn.vertexProperty = function (attributeName, data) { // this._assert3d('vertexProperty'); // p5._validateParameters('vertexProperty', arguments); this._renderer.vertexProperty(attributeName, data); @@ -1192,6 +1192,6 @@ function vertex(p5, fn){ export default vertex; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { vertex(p5, p5.prototype); } diff --git a/src/strands/ir_builders.js b/src/strands/ir_builders.js index 0c4b139d75..2dcbf3debf 100644 --- a/src/strands/ir_builders.js +++ b/src/strands/ir_builders.js @@ -1,7 +1,16 @@ -import * as DAG from './ir_dag' -import * as CFG from './ir_cfg' -import * as FES from './strands_FES' -import { NodeType, OpCode, BaseType, DataType, BasePriority, OpCodeToSymbol, typeEquals, booleanOpCode } from './ir_types'; +import * as DAG from './ir_dag'; +import * as CFG from './ir_cfg'; +import * as FES from './strands_FES'; +import { + NodeType, + OpCode, + BaseType, + DataType, + BasePriority, + OpCodeToSymbol, + typeEquals, + booleanOpCode +} from './ir_types'; import { createStrandsNode, StrandsNode } from './strands_node'; import { strandsBuiltinFunctions } from './strands_builtins'; @@ -9,10 +18,10 @@ import { strandsBuiltinFunctions } from './strands_builtins'; // Builders for node graphs ////////////////////////////////////////////// export function scalarLiteralNode(strandsContext, typeInfo, value) { - const { cfg, dag } = strandsContext + const { cfg, dag } = strandsContext; let { dimension, baseType } = typeInfo; if (dimension !== 1) { - FES.internalError('Created a scalar literal node with dimension > 1.') + FES.internalError('Created a scalar literal node with dimension > 1.'); } const nodeData = DAG.createNodeData({ nodeType: NodeType.LITERAL, @@ -33,7 +42,7 @@ export function variableNode(strandsContext, typeInfo, identifier) { dimension, baseType, identifier - }) + }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); return { id, dimension }; @@ -46,7 +55,11 @@ export function unaryOpNode(strandsContext, nodeOrValue, opCode) { if (nodeOrValue?.isStrandsNode) { node = nodeOrValue; } else { - const { id, dimension } = primitiveConstructorNode(strandsContext, { baseType: BaseType.FLOAT, dimension: null }, nodeOrValue); + const { id, dimension } = primitiveConstructorNode( + strandsContext, + { baseType: BaseType.FLOAT, dimension: null }, + nodeOrValue + ); node = createStrandsNode(id, dimension, strandsContext); } dependsOn = [node.id]; @@ -66,20 +79,29 @@ export function unaryOpNode(strandsContext, nodeOrValue, opCode) { dependsOn, baseType: typeInfo.baseType, dimension: typeInfo.dimension - }) + }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); return { id, dimension: node.dimension }; } -export function binaryOpNode(strandsContext, leftStrandsNode, rightArg, opCode) { +export function binaryOpNode( + strandsContext, + leftStrandsNode, + rightArg, + opCode +) { const { dag, cfg } = strandsContext; // Construct a node for right if its just an array or number etc. let rightStrandsNode; if (rightArg[0] instanceof StrandsNode && rightArg.length === 1) { rightStrandsNode = rightArg[0]; } else { - const { id, dimension } = primitiveConstructorNode(strandsContext, { baseType: BaseType.FLOAT, dimension: null }, rightArg); + const { id, dimension } = primitiveConstructorNode( + strandsContext, + { baseType: BaseType.FLOAT, dimension: null }, + rightArg + ); rightStrandsNode = createStrandsNode(id, dimension, strandsContext); } let finalLeftNodeID = leftStrandsNode.id; @@ -90,68 +112,104 @@ export function binaryOpNode(strandsContext, leftStrandsNode, rightArg, opCode) let rightType = DAG.extractNodeTypeInfo(dag, rightStrandsNode.id); // Update ASSIGN_ON_USE nodes to match the type of the other operand - if (leftType.baseType === BaseType.ASSIGN_ON_USE && rightType.baseType !== BaseType.ASSIGN_ON_USE) { - DAG.propagateTypeToAssignOnUse(dag, leftStrandsNode.id, rightType.baseType, rightType.dimension); + if ( + leftType.baseType === BaseType.ASSIGN_ON_USE && + rightType.baseType !== BaseType.ASSIGN_ON_USE + ) { + DAG.propagateTypeToAssignOnUse( + dag, + leftStrandsNode.id, + rightType.baseType, + rightType.dimension + ); leftType = DAG.extractNodeTypeInfo(dag, leftStrandsNode.id); - } else if (rightType.baseType === BaseType.ASSIGN_ON_USE && leftType.baseType !== BaseType.ASSIGN_ON_USE) { - DAG.propagateTypeToAssignOnUse(dag, rightStrandsNode.id, leftType.baseType, leftType.dimension); + } else if ( + rightType.baseType === BaseType.ASSIGN_ON_USE && + leftType.baseType !== BaseType.ASSIGN_ON_USE + ) { + DAG.propagateTypeToAssignOnUse( + dag, + rightStrandsNode.id, + leftType.baseType, + leftType.dimension + ); rightType = DAG.extractNodeTypeInfo(dag, rightStrandsNode.id); } const cast = { node: null, toType: leftType }; - const bothDeferred = leftType.baseType === rightType.baseType && leftType.baseType === BaseType.DEFER; + const bothDeferred = + leftType.baseType === rightType.baseType && + leftType.baseType === BaseType.DEFER; if (bothDeferred) { cast.toType.baseType = BaseType.FLOAT; if (leftType.dimension === rightType.dimension) { cast.toType.dimension = leftType.dimension; - } - else if (leftType.dimension === 1 && rightType.dimension > 1) { + } else if (leftType.dimension === 1 && rightType.dimension > 1) { cast.toType.dimension = rightType.dimension; - } - else if (rightType.dimension === 1 && leftType.dimension > 1) { + } else if (rightType.dimension === 1 && leftType.dimension > 1) { cast.toType.dimension = leftType.dimension; - } - else { - FES.userError("type error", `You have tried to perform a binary operation:\n`+ - `${leftType.baseType+leftType.dimension} ${OpCodeToSymbol[opCode]} ${rightType.baseType+rightType.dimension}\n` + - `It's only possible to operate on two nodes with the same dimension, or a scalar value and a vector.` + } else { + FES.userError( + 'type error', + `You have tried to perform a binary operation:\n` + + `${leftType.baseType + leftType.dimension} ${OpCodeToSymbol[opCode]} ${rightType.baseType + rightType.dimension}\n` + + `It's only possible to operate on two nodes with the same dimension, or a scalar value and a vector.` ); } - const l = primitiveConstructorNode(strandsContext, cast.toType, leftStrandsNode); - const r = primitiveConstructorNode(strandsContext, cast.toType, rightStrandsNode); + const l = primitiveConstructorNode( + strandsContext, + cast.toType, + leftStrandsNode + ); + const r = primitiveConstructorNode( + strandsContext, + cast.toType, + rightStrandsNode + ); finalLeftNodeID = l.id; finalRightNodeID = r.id; - } - else if (leftType.baseType !== rightType.baseType || - leftType.dimension !== rightType.dimension) { - + } else if ( + leftType.baseType !== rightType.baseType || + leftType.dimension !== rightType.dimension + ) { if (leftType.dimension === 1 && rightType.dimension > 1) { cast.node = leftStrandsNode; cast.toType = rightType; - } - else if (rightType.dimension === 1 && leftType.dimension > 1) { + } else if (rightType.dimension === 1 && leftType.dimension > 1) { cast.node = rightStrandsNode; cast.toType = leftType; - } - else if (leftType.priority > rightType.priority) { + } else if (leftType.priority > rightType.priority) { // e.g. op(float vector, int vector): cast priority is float > int > bool cast.node = rightStrandsNode; cast.toType = leftType; - } - else if (rightType.priority > leftType.priority) { + } else if (rightType.priority > leftType.priority) { cast.node = leftStrandsNode; cast.toType = rightType; - } - else { - FES.userError('type error', `A vector of length ${leftType.dimension} operated with a vector of length ${rightType.dimension} is not allowed.`); + } else { + FES.userError( + 'type error', + `A vector of length ${leftType.dimension} operated with a vector of length ${rightType.dimension} is not allowed.` + ); } - const casted = primitiveConstructorNode(strandsContext, cast.toType, cast.node); + const casted = primitiveConstructorNode( + strandsContext, + cast.toType, + cast.node + ); if (cast.node === leftStrandsNode) { - leftStrandsNode = createStrandsNode(casted.id, casted.dimension, strandsContext); + leftStrandsNode = createStrandsNode( + casted.id, + casted.dimension, + strandsContext + ); finalLeftNodeID = leftStrandsNode.id; } else { - rightStrandsNode = createStrandsNode(casted.id, casted.dimension, strandsContext); + rightStrandsNode = createStrandsNode( + casted.id, + casted.dimension, + strandsContext + ); finalRightNodeID = rightStrandsNode.id; } } @@ -166,28 +224,38 @@ export function binaryOpNode(strandsContext, leftStrandsNode, rightArg, opCode) opCode, dependsOn: [finalLeftNodeID, finalRightNodeID], baseType: cast.toType.baseType, - dimension: cast.toType.dimension, + dimension: cast.toType.dimension }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); return { id, dimension: nodeData.dimension }; } -export function memberAccessNode(strandsContext, parentNode, componentNode, memberTypeInfo) { +export function memberAccessNode( + strandsContext, + parentNode, + componentNode, + memberTypeInfo +) { const { dag, cfg } = strandsContext; const nodeData = DAG.createNodeData({ nodeType: NodeType.OPERATION, opCode: OpCode.Binary.MEMBER_ACCESS, dimension: memberTypeInfo.dimension, baseType: memberTypeInfo.baseType, - dependsOn: [parentNode.id, componentNode.id], + dependsOn: [parentNode.id, componentNode.id] }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); return { id, dimension: memberTypeInfo.dimension }; } -export function structInstanceNode(strandsContext, structTypeInfo, identifier, dependsOn) { +export function structInstanceNode( + strandsContext, + structTypeInfo, + identifier, + dependsOn +) { const { cfg, dag } = strandsContext; if (dependsOn.length === 0) { for (const prop of structTypeInfo.properties) { @@ -196,7 +264,7 @@ export function structInstanceNode(strandsContext, structTypeInfo, identifier, d nodeType: NodeType.VARIABLE, baseType: typeInfo.baseType, dimension: typeInfo.dimension, - identifier: `${identifier}.${prop.name}`, + identifier: `${identifier}.${prop.name}` }); const componentID = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, componentID); @@ -210,7 +278,7 @@ export function structInstanceNode(strandsContext, structTypeInfo, identifier, d baseType: structTypeInfo.typeName, identifier, dependsOn - }) + }); const structID = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, structID); @@ -241,16 +309,22 @@ function mapPrimitiveDepsToIDs(strandsContext, typeInfo, dependsOn) { calculatedDimensions += node.dimension; continue; - } - else if (typeof dep === 'number') { - const { id, dimension } = scalarLiteralNode(strandsContext, { dimension: 1, baseType }, dep); + } else if (typeof dep === 'number') { + const { id, dimension } = scalarLiteralNode( + strandsContext, + { dimension: 1, baseType }, + dep + ); mappedDependencies.push(id); calculatedDimensions += dimension; continue; - } - else if (typeof dep === 'boolean') { + } else if (typeof dep === 'boolean') { // Handle boolean literals - convert to bool type - const { id, dimension } = scalarLiteralNode(strandsContext, { dimension: 1, baseType: BaseType.BOOL }, dep); + const { id, dimension } = scalarLiteralNode( + strandsContext, + { dimension: 1, baseType: BaseType.BOOL }, + dep + ); mappedDependencies.push(id); calculatedDimensions += dimension; // Update baseType to BOOL if it was inferred @@ -258,27 +332,36 @@ function mapPrimitiveDepsToIDs(strandsContext, typeInfo, dependsOn) { baseType = BaseType.BOOL; } continue; - } - else { - FES.userError('type error', `You've tried to construct a scalar or vector type with a non-numeric value: ${dep}`); + } else { + FES.userError( + 'type error', + `You've tried to construct a scalar or vector type with a non-numeric value: ${dep}` + ); } } if (dimension === null) { dimension = calculatedDimensions; } else if (dimension > calculatedDimensions && calculatedDimensions === 1) { calculatedDimensions = dimension; - } else if(calculatedDimensions !== 1 && calculatedDimensions !== dimension) { - FES.userError('type error', `You've tried to construct a ${baseType + dimension} with ${calculatedDimensions} components`); + } else if (calculatedDimensions !== 1 && calculatedDimensions !== dimension) { + FES.userError( + 'type error', + `You've tried to construct a ${baseType + dimension} with ${calculatedDimensions} components` + ); } const inferredTypeInfo = { dimension, baseType, - priority: BasePriority[baseType], - } + priority: BasePriority[baseType] + }; return { originalNodeID, mappedDependencies, inferredTypeInfo }; } -export function constructTypeFromIDs(strandsContext, typeInfo, strandsNodesArray) { +export function constructTypeFromIDs( + strandsContext, + typeInfo, + strandsNodesArray +) { const nodeData = DAG.createNodeData({ nodeType: NodeType.OPERATION, opCode: OpCode.Nary.CONSTRUCTOR, @@ -306,19 +389,28 @@ export function primitiveConstructorNode(strandsContext, typeInfo, dependsOn) { return a; } }); - const { mappedDependencies, inferredTypeInfo } = mapPrimitiveDepsToIDs(strandsContext, typeInfo, dependsOn); + const { mappedDependencies, inferredTypeInfo } = mapPrimitiveDepsToIDs( + strandsContext, + typeInfo, + dependsOn + ); const finalType = { // We might have inferred a non numeric type. Currently this is // just used for booleans. Maybe this needs to be something more robust // if we ever want to support inference of e.g. int vectors? - baseType: inferredTypeInfo.baseType === BaseType.BOOL - ? BaseType.BOOL - : typeInfo.baseType, + baseType: + inferredTypeInfo.baseType === BaseType.BOOL + ? BaseType.BOOL + : typeInfo.baseType, dimension: inferredTypeInfo.dimension }; - const id = constructTypeFromIDs(strandsContext, finalType, mappedDependencies); + const id = constructTypeFromIDs( + strandsContext, + finalType, + mappedDependencies + ); if (typeInfo.baseType !== BaseType.DEFER) { CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); } @@ -332,27 +424,34 @@ export function castToFloat(strandsContext, dep) { strandsContext.backend.getTypeName('float', dep.typeInfo().dimension), [dep], { - overloads: [{ - params: [dep.typeInfo()], - returnType: { - ...dep.typeInfo(), - baseType: BaseType.FLOAT, - }, - }], + overloads: [ + { + params: [dep.typeInfo()], + returnType: { + ...dep.typeInfo(), + baseType: BaseType.FLOAT + } + } + ] } ); return createStrandsNode(id, dimension, strandsContext); } -export function structConstructorNode(strandsContext, structTypeInfo, dependsOn) { +export function structConstructorNode( + strandsContext, + structTypeInfo, + dependsOn +) { const { cfg, dag } = strandsContext; const { properties } = structTypeInfo; if (dependsOn.length !== properties.length) { - FES.userError('type error', + FES.userError( + 'type error', `You've tried to construct a ${structTypeInfo.typeName} struct with ${dependsOn.length} properties, but it expects ${properties.length} properties.\n` + - `The properties it expects are:\n` + - `${properties.map(prop => `${prop.name}: ${prop.dataType.baseType}${prop.dataType.dimension}`).join(', ')}` + `The properties it expects are:\n` + + `${properties.map(prop => `${prop.name}: ${prop.dataType.baseType}${prop.dataType.dimension}`).join(', ')}` ); } @@ -361,34 +460,45 @@ export function structConstructorNode(strandsContext, structTypeInfo, dependsOn) opCode: OpCode.Nary.CONSTRUCTOR, dimension: properties.length, baseType: structTypeInfo.typeName, - dependsOn, + dependsOn }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); - return { id, dimension: properties.length, components: structTypeInfo.components }; + return { + id, + dimension: properties.length, + components: structTypeInfo.components + }; } export function functionCallNode( strandsContext, functionName, rawUserArgs, - { overloads: rawOverloads } = {}, + { overloads: rawOverloads } = {} ) { const { cfg, dag } = strandsContext; const overloads = rawOverloads || strandsBuiltinFunctions[functionName]; - const preprocessedArgs = rawUserArgs.map((rawUserArg) => mapPrimitiveDepsToIDs(strandsContext, DataType.defer, rawUserArg)); - const matchingArgsCounts = overloads.filter(overload => overload.params.length === preprocessedArgs.length); + const preprocessedArgs = rawUserArgs.map(rawUserArg => + mapPrimitiveDepsToIDs(strandsContext, DataType.defer, rawUserArg) + ); + const matchingArgsCounts = overloads.filter( + overload => overload.params.length === preprocessedArgs.length + ); if (matchingArgsCounts.length === 0) { const argsLengthSet = new Set(); const argsLengthArr = []; - overloads.forEach((overload) => argsLengthSet.add(overload.params.length)); - argsLengthSet.forEach((len) => argsLengthArr.push(`${len}`)); + overloads.forEach(overload => argsLengthSet.add(overload.params.length)); + argsLengthSet.forEach(len => argsLengthArr.push(`${len}`)); const argsLengthStr = argsLengthArr.join(', or '); - FES.userError("parameter validation error",`Function '${functionName}' has ${overloads.length} variants which expect ${argsLengthStr} arguments, but ${preprocessedArgs.length} arguments were provided.`); + FES.userError( + 'parameter validation error', + `Function '${functionName}' has ${overloads.length} variants which expect ${argsLengthStr} arguments, but ${preprocessedArgs.length} arguments were provided.` + ); } - const isGeneric = (T) => T.dimension === null; + const isGeneric = T => T.dimension === null; let bestOverload = null; let bestScore = 0; let inferredReturnType = null; @@ -409,14 +519,14 @@ export function functionCallNode( inferredDimension = argType.dimension; } - if (inferredDimension !== argType.dimension && + if ( + inferredDimension !== argType.dimension && !(argType.dimension === 1 && inferredDimension >= 1) - ) { + ) { isValid = false; } dimension = inferredDimension; - } - else { + } else { if (argType.dimension > dimension) { isValid = false; } @@ -424,17 +534,15 @@ export function functionCallNode( if (argType.baseType === expectedType.baseType) { similarity += 2; - } - else if(expectedType.priority > argType.priority) { + } else if (expectedType.priority > argType.priority) { similarity += 1; } - } if (isValid && (!bestOverload || similarity > bestScore)) { bestOverload = overload; bestScore = similarity; - inferredReturnType = {...overload.returnType }; + inferredReturnType = { ...overload.returnType }; if (isGeneric(inferredReturnType)) { inferredReturnType.dimension = inferredDimension; } @@ -442,7 +550,10 @@ export function functionCallNode( } if (bestOverload === null) { - FES.userError('parameter validation', `No matching overload for ${functionName} was found!`); + FES.userError( + 'parameter validation', + `No matching overload for ${functionName} was found!` + ); } let dependsOn = []; @@ -454,9 +565,12 @@ export function functionCallNode( } if (arg.originalNodeID && typeEquals(arg.inferredTypeInfo, paramType)) { dependsOn.push(arg.originalNodeID); - } - else { - const castedArgID = constructTypeFromIDs(strandsContext, paramType, arg.mappedDependencies); + } else { + const castedArgID = constructTypeFromIDs( + strandsContext, + paramType, + arg.mappedDependencies + ); CFG.recordInBasicBlock(cfg, cfg.currentBlock, castedArgID); dependsOn.push(castedArgID); } @@ -469,10 +583,10 @@ export function functionCallNode( dependsOn, baseType: inferredReturnType.baseType, dimension: inferredReturnType.dimension - }) + }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); - return { id, dimension: inferredReturnType.dimension }; + return { id, dimension: inferredReturnType.dimension }; } export function statementNode(strandsContext, statementType) { @@ -495,7 +609,7 @@ export function swizzleNode(strandsContext, parentNode, swizzle) { dimension: swizzle.length, opCode: OpCode.Unary.SWIZZLE, dependsOn: [parentNode.id], - swizzle, + swizzle }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); @@ -503,116 +617,137 @@ export function swizzleNode(strandsContext, parentNode, swizzle) { } export function swizzleTrap(id, dimension, strandsContext, onRebind) { - const swizzleSets = [ - ['x', 'y', 'z', 'w'], - ['r', 'g', 'b', 'a'], - ['s', 't', 'p', 'q'] - ].map(s => s.slice(0, dimension)); - const trap = { - get(target, property, receiver) { - if (property in target) { - return Reflect.get(...arguments); - } else { - for (const set of swizzleSets) { - if ([...property.toString()].every(char => set.includes(char))) { - const swizzle = [...property].map(char => { + const swizzleSets = [ + ['x', 'y', 'z', 'w'], + ['r', 'g', 'b', 'a'], + ['s', 't', 'p', 'q'] + ].map(s => s.slice(0, dimension)); + const trap = { + get(target, property, receiver) { + if (property in target) { + return Reflect.get(...arguments); + } else { + for (const set of swizzleSets) { + if ([...property.toString()].every(char => set.includes(char))) { + const swizzle = [...property] + .map(char => { const index = set.indexOf(char); return swizzleSets[0][index]; - }).join(''); - const node = swizzleNode(strandsContext, target, swizzle); - return createStrandsNode(node.id, node.dimension, strandsContext); - } + }) + .join(''); + const node = swizzleNode(strandsContext, target, swizzle); + return createStrandsNode(node.id, node.dimension, strandsContext); } } - }, - set(target, property, value, receiver) { - for (const swizzleSet of swizzleSets) { - const chars = [...property]; - const valid = - chars.every(c => swizzleSet.includes(c)) && - new Set(chars).size === chars.length && - target.dimension >= chars.length; - if (!valid) continue; - - const dim = target.dimension; - - // lanes are the underlying values of the target vector - // e.g. lane 0 holds the value aliased by 'x', 'r', and 's' - // the lanes array is in the 'correct' order - const lanes = new Array(dim); - for (let i = 0; i < dim; i++) { - const { id, dimension } = swizzleNode(strandsContext, target, 'xyzw'[i]); - lanes[i] = createStrandsNode(id, dimension, strandsContext); } + }, + set(target, property, value, receiver) { + for (const swizzleSet of swizzleSets) { + const chars = [...property]; + const valid = + chars.every(c => swizzleSet.includes(c)) && + new Set(chars).size === chars.length && + target.dimension >= chars.length; + if (!valid) continue; + + const dim = target.dimension; + + // lanes are the underlying values of the target vector + // e.g. lane 0 holds the value aliased by 'x', 'r', and 's' + // the lanes array is in the 'correct' order + const lanes = new Array(dim); + for (let i = 0; i < dim; i++) { + const { id, dimension } = swizzleNode( + strandsContext, + target, + 'xyzw'[i] + ); + lanes[i] = createStrandsNode(id, dimension, strandsContext); + } - // The scalars array contains the individual components of the users values. - // This may not be the most efficient way, as we swizzle each component individually, - // so that .xyz becomes .x, .y, .z - let scalars = []; - if (value?.isStrandsNode) { - if (value.dimension === 1) { - scalars = Array(chars.length).fill(value); - } else if (value.dimension === chars.length) { - for (let k = 0; k < chars.length; k++) { - const { id, dimension } = swizzleNode(strandsContext, value, 'xyzw'[k]); - scalars.push(createStrandsNode(id, dimension, strandsContext)); + // The scalars array contains the individual components of the users values. + // This may not be the most efficient way, as we swizzle each component individually, + // so that .xyz becomes .x, .y, .z + let scalars = []; + if (value?.isStrandsNode) { + if (value.dimension === 1) { + scalars = Array(chars.length).fill(value); + } else if (value.dimension === chars.length) { + for (let k = 0; k < chars.length; k++) { + const { id, dimension } = swizzleNode( + strandsContext, + value, + 'xyzw'[k] + ); + scalars.push(createStrandsNode(id, dimension, strandsContext)); + } + } else { + FES.dimensionMismatchError( + chars.length, + value.dimension, + `${target._originalIdentifier || 'value'}.${property}` + ); } + } else if (Array.isArray(value)) { + const flat = value.flat(Infinity); + if (flat.length === 1) { + scalars = Array(chars.length).fill(flat[0]); + } else if (flat.length === chars.length) { + scalars = flat; + } else { + FES.userError( + 'type error', + `Swizzle assignment: RHS length ${flat.length} does not match ${chars.length}.` + ); + } + } else if (typeof value === 'number') { + scalars = Array(chars.length).fill(value); } else { - FES.dimensionMismatchError( - chars.length, - value.dimension, - `${target._originalIdentifier || 'value'}.${property}` + FES.userError( + 'type error', + `Unsupported RHS for swizzle assignment: ${value}` ); } - } else if (Array.isArray(value)) { - const flat = value.flat(Infinity); - if (flat.length === 1) { - scalars = Array(chars.length).fill(flat[0]); - } else if (flat.length === chars.length) { - scalars = flat; - } else { - FES.userError('type error', `Swizzle assignment: RHS length ${flat.length} does not match ${chars.length}.`); - } - } else if (typeof value === 'number') { - scalars = Array(chars.length).fill(value); - } else { - FES.userError('type error', `Unsupported RHS for swizzle assignment: ${value}`); - } - // The canonical index refers to the actual value's position in the vector lanes - // i.e. we are finding (3,2,1) from .zyx - // We set the correct value in the lanes array - for (let j = 0; j < chars.length; j++) { - const canonicalIndex = swizzleSet.indexOf(chars[j]); - lanes[canonicalIndex] = scalars[j]; - } - - const orig = DAG.getNodeDataFromID(strandsContext.dag, target.id); - const baseType = orig?.baseType ?? BaseType.FLOAT; - const { id: newID } = primitiveConstructorNode( - strandsContext, - { baseType, dimension: dim }, - lanes - ); - - target.id = newID; + // The canonical index refers to the actual value's position in the vector lanes + // i.e. we are finding (3,2,1) from .zyx + // We set the correct value in the lanes array + for (let j = 0; j < chars.length; j++) { + const canonicalIndex = swizzleSet.indexOf(chars[j]); + lanes[canonicalIndex] = scalars[j]; + } - // If we swizzle assign on a struct component i.e. - // inputs.position.rg = [1, 2] - // The onRebind callback will update the structs components so that it refers to the new values, - // and make a new ID for the struct with these new values - if (typeof onRebind === 'function') { - onRebind(newID); + const orig = DAG.getNodeDataFromID(strandsContext.dag, target.id); + const baseType = orig?.baseType ?? BaseType.FLOAT; + const { id: newID } = primitiveConstructorNode( + strandsContext, + { baseType, dimension: dim }, + lanes + ); + + target.id = newID; + + // If we swizzle assign on a struct component i.e. + // inputs.position.rg = [1, 2] + // The onRebind callback will update the structs components so that it refers to the new values, + // and make a new ID for the struct with these new values + if (typeof onRebind === 'function') { + onRebind(newID); + } + return true; } - return true; + return Reflect.set(...arguments); } - return Reflect.set(...arguments); - } }; return trap; } -export function arrayAccessNode(strandsContext, bufferNode, indexNode, accessMode) { +export function arrayAccessNode( + strandsContext, + bufferNode, + indexNode, + accessMode +) { const { dag, cfg } = strandsContext; // Ensure index is a StrandsNode @@ -644,7 +779,12 @@ export function arrayAccessNode(strandsContext, bufferNode, indexNode, accessMod return { id, dimension: 1 }; } -export function createStructArrayElementProxy(strandsContext, bufferNode, indexNode, schema) { +export function createStructArrayElementProxy( + strandsContext, + bufferNode, + indexNode, + schema +) { const { dag, cfg } = strandsContext; // Ensure index is a StrandsNode @@ -675,27 +815,27 @@ export function createStructArrayElementProxy(strandsContext, bufferNode, indexN dependsOn: [bufferNode.id, index.id], dimension: field.dim, baseType: BaseType.FLOAT, - identifier: field.name, + identifier: field.name }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); // When a swizzle assignment fires (e.g. buf[i].vel.y *= -1), onRebind // receives the new vector ID and writes it back to the buffer field, // equivalent to buf[i].vel = newVec. - const onRebind = (newFieldID) => { + const onRebind = newFieldID => { const accessData = DAG.createNodeData({ nodeType: NodeType.OPERATION, opCode: OpCode.Binary.ARRAY_ACCESS, dependsOn: [bufferNode.id, index.id], dimension: field.dim, baseType: BaseType.FLOAT, - identifier: field.name, + identifier: field.name }); const accessID = DAG.getOrCreateNode(dag, accessData); const assignData = DAG.createNodeData({ nodeType: NodeType.ASSIGNMENT, dependsOn: [accessID, newFieldID], - phiBlocks: [], + phiBlocks: [] }); const assignID = DAG.getOrCreateNode(dag, assignData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, assignID); @@ -710,7 +850,7 @@ export function createStructArrayElementProxy(strandsContext, bufferNode, indexN dependsOn: [bufferNode.id, index.id], dimension: field.dim, baseType: BaseType.FLOAT, - identifier: field.name, + identifier: field.name }); const accessID = DAG.getOrCreateNode(dag, accessData); @@ -729,19 +869,24 @@ export function createStructArrayElementProxy(strandsContext, bufferNode, indexN const assignData = DAG.createNodeData({ nodeType: NodeType.ASSIGNMENT, dependsOn: [accessID, valueID], - phiBlocks: [], + phiBlocks: [] }); const assignID = DAG.getOrCreateNode(dag, assignData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, assignID); }, - configurable: true, + configurable: true }); } return proxy; } -export function arrayAssignmentNode(strandsContext, bufferNode, indexNode, valueNode) { +export function arrayAssignmentNode( + strandsContext, + bufferNode, + indexNode, + valueNode +) { const { dag, cfg } = strandsContext; // Ensure index is a StrandsNode diff --git a/src/strands/ir_cfg.js b/src/strands/ir_cfg.js index cf25a23d53..09f26d429e 100644 --- a/src/strands/ir_cfg.js +++ b/src/strands/ir_cfg.js @@ -1,5 +1,5 @@ -import { BlockTypeToName } from "./ir_types"; -import * as FES from './strands_FES' +import { BlockTypeToName } from './ir_types'; +import * as FES from './strands_FES'; // Todo: remove edges to simplify. Block order is always ordered already. @@ -15,7 +15,7 @@ export function createControlFlowGraph() { blockStack: [], blockOrder: [], blockConditions: {}, - currentBlock: -1, + currentBlock: -1 }; } @@ -28,7 +28,7 @@ export function pushBlock(graph, blockID) { export function popBlock(graph) { graph.blockStack.pop(); const len = graph.blockStack.length; - graph.currentBlock = graph.blockStack[len-1]; + graph.currentBlock = graph.blockStack[len - 1]; } export function pushBlockForModification(graph, blockID) { @@ -41,7 +41,7 @@ export function createBasicBlock(graph, blockType) { graph.blockTypes[id] = blockType; graph.incomingEdges[id] = []; graph.outgoingEdges[id] = []; - graph.blockInstructions[id]= []; + graph.blockInstructions[id] = []; return id; } @@ -67,8 +67,8 @@ export function getBlockDataFromID(graph, id) { blockType: graph.blockTypes[id], incomingEdges: graph.incomingEdges[id], outgoingEdges: graph.outgoingEdges[id], - blockInstructions: graph.blockInstructions[id], - } + blockInstructions: graph.blockInstructions[id] + }; } export function printBlockData(graph, id) { @@ -85,7 +85,7 @@ export function sortCFG(adjacencyList, start) { return; } visited.add(v); - for (let w of adjacencyList[v].sort((a, b) => b-a) || []) { + for (let w of adjacencyList[v].sort((a, b) => b - a) || []) { dfs(w); } postOrder.push(v); @@ -93,4 +93,4 @@ export function sortCFG(adjacencyList, start) { dfs(start); return postOrder.reverse(); -} \ No newline at end of file +} diff --git a/src/strands/ir_dag.js b/src/strands/ir_dag.js index b5a19c1cf6..31dbb474f4 100644 --- a/src/strands/ir_dag.js +++ b/src/strands/ir_dag.js @@ -1,4 +1,10 @@ -import { NodeTypeRequiredFields, NodeTypeToName, BasePriority, StatementType, BaseType } from './ir_types'; +import { + NodeTypeRequiredFields, + NodeTypeToName, + BasePriority, + StatementType, + BaseType +} from './ir_types'; import * as FES from './strands_FES'; ///////////////////////////////// @@ -19,7 +25,7 @@ export function createDirectedAcyclicGraph() { dependsOn: [], usedBy: [], statementTypes: [], - swizzles: [], + swizzles: [] }; return graph; @@ -30,11 +36,11 @@ export function getOrCreateNode(graph, node) { // const existing = graph.cache.get(key); // if (existing !== undefined) { - // return existing; + // return existing; // } else { - const id = createNode(graph, node); - // graph.cache.set(key, id); - return id; + const id = createNode(graph, node); + // graph.cache.set(key, id); + return id; // } } @@ -50,7 +56,7 @@ export function createNodeData(data = {}) { swizzle: data.swizzle ?? null, dependsOn: Array.isArray(data.dependsOn) ? data.dependsOn : [], usedBy: Array.isArray(data.usedBy) ? data.usedBy : [], - phiBlocks: Array.isArray(data.phiBlocks) ? data.phiBlocks : [], + phiBlocks: Array.isArray(data.phiBlocks) ? data.phiBlocks : [] }; validateNode(node); return node; @@ -69,20 +75,26 @@ export function getNodeDataFromID(graph, id) { dimension: graph.dimensions[id], baseType: graph.baseTypes[id], statementType: graph.statementTypes[id], - swizzle: graph.swizzles[id], - } + swizzle: graph.swizzles[id] + }; } export function extractNodeTypeInfo(dag, nodeID) { return { baseType: dag.baseTypes[nodeID], dimension: dag.dimensions[nodeID], - priority: BasePriority[dag.baseTypes[nodeID]], + priority: BasePriority[dag.baseTypes[nodeID]] }; } // Propagate a known type to an ASSIGN_ON_USE node and all its ASSIGN_ON_USE dependencies -export function propagateTypeToAssignOnUse(dag, nodeId, baseType, dimension, visited = new Set()) { +export function propagateTypeToAssignOnUse( + dag, + nodeId, + baseType, + dimension, + visited = new Set() +) { // Avoid infinite loops if (visited.has(nodeId)) { return; @@ -123,7 +135,7 @@ function createNode(graph, node) { graph.dependsOn[id] = node.dependsOn.slice(); graph.usedBy[id] = node.usedBy; graph.phiBlocks[id] = node.phiBlocks.slice(); - graph.baseTypes[id] = node.baseType + graph.baseTypes[id] = node.baseType; graph.dimensions[id] = node.dimension; graph.statementTypes[id] = node.statementType; graph.swizzles[id] = node.swizzle; @@ -142,11 +154,13 @@ function getNodeKey(node) { return key; } -function validateNode(node){ +function validateNode(node) { const nodeType = node.nodeType; const requiredFields = NodeTypeRequiredFields[nodeType]; if (requiredFields.length === 2) { - FES.internalError(`Required fields for node type '${NodeTypeToName[nodeType]}' not defined. Please add them to the utils.js file in p5.strands!`) + FES.internalError( + `Required fields for node type '${NodeTypeToName[nodeType]}' not defined. Please add them to the utils.js file in p5.strands!` + ); } const missingFields = []; for (const field of requiredFields) { @@ -158,6 +172,8 @@ function validateNode(node){ throw new Error('Undefined dependency!'); } if (missingFields.length > 0) { - FES.internalError(`Missing fields ${missingFields.join(', ')} for a node type '${NodeTypeToName[nodeType]}'.`); + FES.internalError( + `Missing fields ${missingFields.join(', ')} for a node type '${NodeTypeToName[nodeType]}'.` + ); } } diff --git a/src/strands/ir_types.js b/src/strands/ir_types.js index 20a432ffdc..65a17769b1 100644 --- a/src/strands/ir_types.js +++ b/src/strands/ir_types.js @@ -9,7 +9,7 @@ export const NodeType = { STRUCT: 'struct', PHI: 'phi', STATEMENT: 'statement', - ASSIGNMENT: 'assignment', + ASSIGNMENT: 'assignment' }; export const INSTANCE_ID_VARYING_NAME = '_p5_instanceID'; export const HOOK_PARAM_PREFIX = '_p5_param_'; @@ -17,31 +17,31 @@ export const NodeTypeToName = Object.fromEntries( Object.entries(NodeType).map(([key, val]) => [val, key]) ); export const NodeTypeRequiredFields = { - [NodeType.OPERATION]: ["opCode", "dependsOn", "dimension", "baseType"], - [NodeType.LITERAL]: ["value", "dimension", "baseType"], - [NodeType.VARIABLE]: ["identifier", "dimension", "baseType"], - [NodeType.CONSTANT]: ["value", "dimension", "baseType"], - [NodeType.STRUCT]: [""], - [NodeType.PHI]: ["dependsOn", "phiBlocks", "dimension", "baseType"], - [NodeType.STATEMENT]: ["statementType"], - [NodeType.ASSIGNMENT]: ["dependsOn"] + [NodeType.OPERATION]: ['opCode', 'dependsOn', 'dimension', 'baseType'], + [NodeType.LITERAL]: ['value', 'dimension', 'baseType'], + [NodeType.VARIABLE]: ['identifier', 'dimension', 'baseType'], + [NodeType.CONSTANT]: ['value', 'dimension', 'baseType'], + [NodeType.STRUCT]: [''], + [NodeType.PHI]: ['dependsOn', 'phiBlocks', 'dimension', 'baseType'], + [NodeType.STATEMENT]: ['statementType'], + [NodeType.ASSIGNMENT]: ['dependsOn'] }; export const StatementType = { DISCARD: 'discard', BREAK: 'break', EARLY_RETURN: 'early_return', EXPRESSION: 'expression', // Used when we want to output a single expression as a statement, e.g. a for loop condition - EMPTY: 'empty', // Used for empty statements like ; in for loops + EMPTY: 'empty' // Used for empty statements like ; in for loops }; export const BaseType = { - FLOAT: "float", - INT: "int", - BOOL: "bool", - MAT: "mat", - DEFER: "defer", - ASSIGN_ON_USE: "assign_on_use", - SAMPLER2D: "sampler2D", - SAMPLER: "sampler", + FLOAT: 'float', + INT: 'int', + BOOL: 'bool', + MAT: 'mat', + DEFER: 'defer', + ASSIGN_ON_USE: 'assign_on_use', + SAMPLER2D: 'sampler2D', + SAMPLER: 'sampler' }; export const BasePriority = { [BaseType.FLOAT]: 3, @@ -51,42 +51,95 @@ export const BasePriority = { [BaseType.DEFER]: -1, [BaseType.ASSIGN_ON_USE]: -2, [BaseType.SAMPLER2D]: -10, - [BaseType.SAMPLER]: -11, + [BaseType.SAMPLER]: -11 }; export const DataType = { - float1: { fnName: "float", baseType: BaseType.FLOAT, dimension:1, priority: 3, }, - float2: { fnName: "vec2", baseType: BaseType.FLOAT, dimension:2, priority: 3, }, - float3: { fnName: "vec3", baseType: BaseType.FLOAT, dimension:3, priority: 3, }, - float4: { fnName: "vec4", baseType: BaseType.FLOAT, dimension:4, priority: 3, }, - int1: { fnName: "int", baseType: BaseType.INT, dimension:1, priority: 2, }, - int2: { fnName: "ivec2", baseType: BaseType.INT, dimension:2, priority: 2, }, - int3: { fnName: "ivec3", baseType: BaseType.INT, dimension:3, priority: 2, }, - int4: { fnName: "ivec4", baseType: BaseType.INT, dimension:4, priority: 2, }, - bool1: { fnName: "bool", baseType: BaseType.BOOL, dimension:1, priority: 1, }, - bool2: { fnName: "bvec2", baseType: BaseType.BOOL, dimension:2, priority: 1, }, - bool3: { fnName: "bvec3", baseType: BaseType.BOOL, dimension:3, priority: 1, }, - bool4: { fnName: "bvec4", baseType: BaseType.BOOL, dimension:4, priority: 1, }, - mat2: { fnName: "mat2x2", baseType: BaseType.MAT, dimension:2, priority: 0, }, - mat3: { fnName: "mat3x3", baseType: BaseType.MAT, dimension:3, priority: 0, }, - mat4: { fnName: "mat4x4", baseType: BaseType.MAT, dimension:4, priority: 0, }, - defer: { fnName: null, baseType: BaseType.DEFER, dimension: null, priority: -1 }, - assign_on_use: { fnName: null, baseType: BaseType.ASSIGN_ON_USE, dimension: null, priority: -2 }, - sampler2D: { fnName: "sampler2D", baseType: BaseType.SAMPLER2D, dimension: 1, priority: -10 }, - sampler: { fnName: "sampler", baseType: BaseType.SAMPLER, dimension: 1, priority: -11 }, -} + float1: { + fnName: 'float', + baseType: BaseType.FLOAT, + dimension: 1, + priority: 3 + }, + float2: { + fnName: 'vec2', + baseType: BaseType.FLOAT, + dimension: 2, + priority: 3 + }, + float3: { + fnName: 'vec3', + baseType: BaseType.FLOAT, + dimension: 3, + priority: 3 + }, + float4: { + fnName: 'vec4', + baseType: BaseType.FLOAT, + dimension: 4, + priority: 3 + }, + int1: { fnName: 'int', baseType: BaseType.INT, dimension: 1, priority: 2 }, + int2: { fnName: 'ivec2', baseType: BaseType.INT, dimension: 2, priority: 2 }, + int3: { fnName: 'ivec3', baseType: BaseType.INT, dimension: 3, priority: 2 }, + int4: { fnName: 'ivec4', baseType: BaseType.INT, dimension: 4, priority: 2 }, + bool1: { fnName: 'bool', baseType: BaseType.BOOL, dimension: 1, priority: 1 }, + bool2: { + fnName: 'bvec2', + baseType: BaseType.BOOL, + dimension: 2, + priority: 1 + }, + bool3: { + fnName: 'bvec3', + baseType: BaseType.BOOL, + dimension: 3, + priority: 1 + }, + bool4: { + fnName: 'bvec4', + baseType: BaseType.BOOL, + dimension: 4, + priority: 1 + }, + mat2: { fnName: 'mat2x2', baseType: BaseType.MAT, dimension: 2, priority: 0 }, + mat3: { fnName: 'mat3x3', baseType: BaseType.MAT, dimension: 3, priority: 0 }, + mat4: { fnName: 'mat4x4', baseType: BaseType.MAT, dimension: 4, priority: 0 }, + defer: { + fnName: null, + baseType: BaseType.DEFER, + dimension: null, + priority: -1 + }, + assign_on_use: { + fnName: null, + baseType: BaseType.ASSIGN_ON_USE, + dimension: null, + priority: -2 + }, + sampler2D: { + fnName: 'sampler2D', + baseType: BaseType.SAMPLER2D, + dimension: 1, + priority: -10 + }, + sampler: { + fnName: 'sampler', + baseType: BaseType.SAMPLER, + dimension: 1, + priority: -11 + } +}; export const structType = function (hookType) { let T = hookType.type === undefined ? hookType : hookType.type; const structType = { name: hookType.name, properties: [], - typeName: T.typeName, + typeName: T.typeName }; // TODO: handle struct properties that are themselves structs for (const prop of T.properties) { const propType = prop.type.dataType; - structType.properties.push( - {name: prop.name, dataType: propType } - ); + structType.properties.push({ name: prop.name, dataType: propType }); } return structType; }; @@ -96,10 +149,12 @@ export function isStructType(typeInfo) { export const GenType = { FLOAT: { baseType: BaseType.FLOAT, dimension: null, priority: 3 }, INT: { baseType: BaseType.INT, dimension: null, priority: 2 }, - BOOL: { baseType: BaseType.BOOL, dimension: null, priority: 1 }, -} + BOOL: { baseType: BaseType.BOOL, dimension: null, priority: 1 } +}; export function typeEquals(nodeA, nodeB) { - return (nodeA.dimension === nodeB.dimension) && (nodeA.baseType === nodeB.baseType); + return ( + nodeA.dimension === nodeB.dimension && nodeA.baseType === nodeB.baseType + ); } export const TypeInfoFromGLSLName = Object.fromEntries( Object.values(DataType) @@ -122,45 +177,104 @@ export const OpCode = { LOGICAL_AND: 11, LOGICAL_OR: 12, MEMBER_ACCESS: 13, - ARRAY_ACCESS: 14, + ARRAY_ACCESS: 14 }, Unary: { LOGICAL_NOT: 100, NEGATE: 101, PLUS: 102, - SWIZZLE: 103, + SWIZZLE: 103 }, Nary: { FUNCTION_CALL: 200, CONSTRUCTOR: 201, TERNARY: 202, - ARRAY_ASSIGNMENT: 203, + ARRAY_ASSIGNMENT: 203 }, ControlFlow: { RETURN: 300, JUMP: 301, BRANCH_IF_FALSE: 302, DISCARD: 303, - BREAK: 304, + BREAK: 304 } }; export const OperatorTable = [ - { arity: "unary", boolean: true, name: "not", symbol: "!", opCode: OpCode.Unary.LOGICAL_NOT }, - { arity: "unary", name: "neg", symbol: "-", opCode: OpCode.Unary.NEGATE }, - { arity: "unary", name: "plus", symbol: "+", opCode: OpCode.Unary.PLUS }, - { arity: "binary", name: "add", symbol: "+", opCode: OpCode.Binary.ADD }, - { arity: "binary", name: "sub", symbol: "-", opCode: OpCode.Binary.SUBTRACT }, - { arity: "binary", name: "mult", symbol: "*", opCode: OpCode.Binary.MULTIPLY }, - { arity: "binary", name: "div", symbol: "/", opCode: OpCode.Binary.DIVIDE }, - { arity: "binary", name: "mod", symbol: "%", opCode: OpCode.Binary.MODULO }, - { arity: "binary", boolean: true, name: "equalTo", symbol: "==", opCode: OpCode.Binary.EQUAL }, - { arity: "binary", boolean: true, name: "notEqual", symbol: "!=", opCode: OpCode.Binary.NOT_EQUAL }, - { arity: "binary", boolean: true, name: "greaterThan", symbol: ">", opCode: OpCode.Binary.GREATER_THAN }, - { arity: "binary", boolean: true, name: "greaterEqual", symbol: ">=", opCode: OpCode.Binary.GREATER_EQUAL }, - { arity: "binary", boolean: true, name: "lessThan", symbol: "<", opCode: OpCode.Binary.LESS_THAN }, - { arity: "binary", boolean: true, name: "lessEqual", symbol: "<=", opCode: OpCode.Binary.LESS_EQUAL }, - { arity: "binary", boolean: true, name: "and", symbol: "&&", opCode: OpCode.Binary.LOGICAL_AND }, - { arity: "binary", boolean: true, name: "or", symbol: "||", opCode: OpCode.Binary.LOGICAL_OR }, + { + arity: 'unary', + boolean: true, + name: 'not', + symbol: '!', + opCode: OpCode.Unary.LOGICAL_NOT + }, + { arity: 'unary', name: 'neg', symbol: '-', opCode: OpCode.Unary.NEGATE }, + { arity: 'unary', name: 'plus', symbol: '+', opCode: OpCode.Unary.PLUS }, + { arity: 'binary', name: 'add', symbol: '+', opCode: OpCode.Binary.ADD }, + { arity: 'binary', name: 'sub', symbol: '-', opCode: OpCode.Binary.SUBTRACT }, + { + arity: 'binary', + name: 'mult', + symbol: '*', + opCode: OpCode.Binary.MULTIPLY + }, + { arity: 'binary', name: 'div', symbol: '/', opCode: OpCode.Binary.DIVIDE }, + { arity: 'binary', name: 'mod', symbol: '%', opCode: OpCode.Binary.MODULO }, + { + arity: 'binary', + boolean: true, + name: 'equalTo', + symbol: '==', + opCode: OpCode.Binary.EQUAL + }, + { + arity: 'binary', + boolean: true, + name: 'notEqual', + symbol: '!=', + opCode: OpCode.Binary.NOT_EQUAL + }, + { + arity: 'binary', + boolean: true, + name: 'greaterThan', + symbol: '>', + opCode: OpCode.Binary.GREATER_THAN + }, + { + arity: 'binary', + boolean: true, + name: 'greaterEqual', + symbol: '>=', + opCode: OpCode.Binary.GREATER_EQUAL + }, + { + arity: 'binary', + boolean: true, + name: 'lessThan', + symbol: '<', + opCode: OpCode.Binary.LESS_THAN + }, + { + arity: 'binary', + boolean: true, + name: 'lessEqual', + symbol: '<=', + opCode: OpCode.Binary.LESS_EQUAL + }, + { + arity: 'binary', + boolean: true, + name: 'and', + symbol: '&&', + opCode: OpCode.Binary.LOGICAL_AND + }, + { + arity: 'binary', + boolean: true, + name: 'or', + symbol: '||', + opCode: OpCode.Binary.LOGICAL_OR + } ]; export const ConstantFolding = { [OpCode.Binary.ADD]: (a, b) => a + b, @@ -175,7 +289,7 @@ export const ConstantFolding = { [OpCode.Binary.LESS_THAN]: (a, b) => a < b, [OpCode.Binary.LESS_EQUAL]: (a, b) => a <= b, [OpCode.Binary.LOGICAL_AND]: (a, b) => a && b, - [OpCode.Binary.LOGICAL_OR]: (a, b) => a || b, + [OpCode.Binary.LOGICAL_OR]: (a, b) => a || b }; // export const SymbolToOpCode = {}; export const OpCodeToSymbol = {}; @@ -206,8 +320,8 @@ export const BlockType = { SCOPE_END: 'scope_end', FOR: 'for', MERGE: 'merge', - DEFAULT: 'default', -} + DEFAULT: 'default' +}; export const BlockTypeToName = Object.fromEntries( Object.entries(BlockType).map(([key, val]) => [val, key]) ); diff --git a/src/strands/p5.strands.js b/src/strands/p5.strands.js index c7b6a9a8a2..baa6f7b046 100644 --- a/src/strands/p5.strands.js +++ b/src/strands/p5.strands.js @@ -4,21 +4,21 @@ * @for p5 */ -import { transpileStrandsToJS } from "./strands_transpiler"; -import { BlockType } from "./ir_types"; +import { transpileStrandsToJS } from './strands_transpiler'; +import { BlockType } from './ir_types'; -import { createDirectedAcyclicGraph } from "./ir_dag"; +import { createDirectedAcyclicGraph } from './ir_dag'; import { createControlFlowGraph, createBasicBlock, pushBlock, - popBlock, -} from "./ir_cfg"; -import { generateShaderCode } from "./strands_codegen"; + popBlock +} from './ir_cfg'; +import { generateShaderCode } from './strands_codegen'; import { initGlobalStrandsAPI, - createShaderHooksFunctions, -} from "./strands_api"; + createShaderHooksFunctions +} from './strands_api'; function strands(p5, fn) { // Whether or not strands callbacks should be forced to be executed in global mode. @@ -32,7 +32,7 @@ function strands(p5, fn) { function initStrandsContext( ctx, backend, - { active = false, renderer = null, baseShader = null } = {}, + { active = false, renderer = null, baseShader = null } = {} ) { ctx.dag = createDirectedAcyclicGraph(); ctx.cfg = createControlFlowGraph(); @@ -96,7 +96,7 @@ function strands(p5, fn) { const prev = {}; for (const key of Object.getOwnPropertyNames(fn)) { const descriptor = Object.getOwnPropertyDescriptor(fn, key); - if (descriptor && !descriptor.get && typeof fn[key] === "function") { + if (descriptor && !descriptor.get && typeof fn[key] === 'function') { prev[key] = window[key]; window[key] = fn[key].bind(pInst); } @@ -116,21 +116,25 @@ function strands(p5, fn) { ////////////////////////////////////////////// const oldModify = p5.Shader.prototype.modify; - p5.Shader.prototype.modify = function (shaderModifier, scope = {}, options = {}) { + p5.Shader.prototype.modify = function ( + shaderModifier, + scope = {}, + options = {} + ) { const fnOverrides = {}; const windowOverrides = {}; const graphicsOverrides = {}; try { if ( shaderModifier instanceof Function || - typeof shaderModifier === "string" + typeof shaderModifier === 'string' ) { // Reset the context object every time modify is called; // const backend = glslBackend; initStrandsContext(strandsContext, this._renderer.strandsBackend, { active: true, renderer: this._renderer, - baseShader: this, + baseShader: this }); createShaderHooksFunctions(strandsContext, fn, this); // TODO: expose this, is internal for debugging for now. @@ -143,14 +147,14 @@ function strands(p5, fn) { // #7955 Wrap function declaration code in brackets so anonymous functions are not top level statements, which causes an error in acorn when parsing // https://github.com/acornjs/acorn/issues/1385 const sourceString = - typeof shaderModifier === "string" + typeof shaderModifier === 'string' ? `(${shaderModifier})` : `(${shaderModifier.toString()})`; strandsCallback = transpileStrandsToJS( p5, sourceString, options.srcLocations, - scope, + scope ); } else { strandsCallback = shaderModifier; @@ -159,12 +163,13 @@ function strands(p5, fn) { // 2. Build the IR from JavaScript API const globalScope = createBasicBlock( strandsContext.cfg, - BlockType.GLOBAL, + BlockType.GLOBAL ); pushBlock(strandsContext.cfg, globalScope); if (options.hook) { strandsContext.renderer._pInst[options.hook].begin(); - for (const key of strandsContext.renderer._pInst[options.hook]._properties) { + for (const key of strandsContext.renderer._pInst[options.hook] + ._properties) { const hookProp = strandsContext.renderer._pInst[options.hook][key]; fnOverrides[key] = fn[key]; fn[key] = hookProp; @@ -209,7 +214,7 @@ function strands(p5, fn) { export default strands; -if (typeof p5 !== "undefined") { +if (typeof p5 !== 'undefined') { p5.registerAddon(strands); } diff --git a/src/strands/strands_FES.js b/src/strands/strands_FES.js index 245ea4a302..a2a608add9 100644 --- a/src/strands/strands_FES.js +++ b/src/strands/strands_FES.js @@ -1,16 +1,16 @@ export function internalError(errorMessage) { - const prefixedMessage = `[p5.strands internal error]: ${errorMessage}` - throw new Error(prefixedMessage); + const prefixedMessage = `[p5.strands internal error]: ${errorMessage}`; + throw new Error(prefixedMessage); } export function userError(errorType, errorMessage) { - const prefixedMessage = `[p5.strands ${errorType}]: ${errorMessage}`; - throw new Error(prefixedMessage); + const prefixedMessage = `[p5.strands ${errorType}]: ${errorMessage}`; + throw new Error(prefixedMessage); } -export function dimensionMismatchError(declaredDim,actualDim,varName){ - userError( - 'dimension mismatch', +export function dimensionMismatchError(declaredDim, actualDim, varName) { + userError( + 'dimension mismatch', `Cannot assign a value of dimension ${actualDim} to \`${varName}\`, which expects dimension ${declaredDim}.` - ); -} \ No newline at end of file + ); +} diff --git a/src/strands/strands_api.js b/src/strands/strands_api.js index b75a9de25b..e4a21cb8bd 100644 --- a/src/strands/strands_api.js +++ b/src/strands/strands_api.js @@ -1,4 +1,4 @@ -import * as build from './ir_builders' +import * as build from './ir_builders'; import { OperatorTable, BlockType, @@ -10,52 +10,60 @@ import { OpCode, StatementType, NodeType, - HOOK_PARAM_PREFIX, + HOOK_PARAM_PREFIX // isNativeType -} from './ir_types' -import { strandsBuiltinFunctions } from './strands_builtins' -import { StrandsConditional } from './strands_conditionals' -import { StrandsFor } from './strands_for' -import { buildTernary } from './strands_ternary' -import * as CFG from './ir_cfg' +} from './ir_types'; +import { strandsBuiltinFunctions } from './strands_builtins'; +import { StrandsConditional } from './strands_conditionals'; +import { StrandsFor } from './strands_for'; +import { buildTernary } from './strands_ternary'; +import * as CFG from './ir_cfg'; import * as DAG from './ir_dag'; -import * as FES from './strands_FES' -import { getNodeDataFromID } from './ir_dag' -import { StrandsNode, createStrandsNode } from './strands_node' +import * as FES from './strands_FES'; +import { getNodeDataFromID } from './ir_dag'; +import { StrandsNode, createStrandsNode } from './strands_node'; const BUILTIN_GLOBAL_SPECS = { - width: { typeInfo: DataType.float1, get: (p) => p.width }, - height: { typeInfo: DataType.float1, get: (p) => p.height }, - mouseX: { typeInfo: DataType.float1, get: (p) => p.mouseX }, - mouseY: { typeInfo: DataType.float1, get: (p) => p.mouseY }, - pmouseX: { typeInfo: DataType.float1, get: (p) => p.pmouseX }, - pmouseY: { typeInfo: DataType.float1, get: (p) => p.pmouseY }, - winMouseX: { typeInfo: DataType.float1, get: (p) => p.winMouseX }, - winMouseY: { typeInfo: DataType.float1, get: (p) => p.winMouseY }, - pwinMouseX: { typeInfo: DataType.float1, get: (p) => p.pwinMouseX }, - pwinMouseY: { typeInfo: DataType.float1, get: (p) => p.pwinMouseY }, - frameCount: { typeInfo: DataType.float1, get: (p) => p.frameCount }, - deltaTime: { typeInfo: DataType.float1, get: (p) => p.deltaTime }, - displayWidth: { typeInfo: DataType.float1, get: (p) => p.displayWidth }, - displayHeight: { typeInfo: DataType.float1, get: (p) => p.displayHeight }, - windowWidth: { typeInfo: DataType.float1, get: (p) => p.windowWidth }, - windowHeight: { typeInfo: DataType.float1, get: (p) => p.windowHeight }, - mouseIsPressed: { typeInfo: DataType.bool1, get: (p) => p.mouseIsPressed }, -} + width: { typeInfo: DataType.float1, get: p => p.width }, + height: { typeInfo: DataType.float1, get: p => p.height }, + mouseX: { typeInfo: DataType.float1, get: p => p.mouseX }, + mouseY: { typeInfo: DataType.float1, get: p => p.mouseY }, + pmouseX: { typeInfo: DataType.float1, get: p => p.pmouseX }, + pmouseY: { typeInfo: DataType.float1, get: p => p.pmouseY }, + winMouseX: { typeInfo: DataType.float1, get: p => p.winMouseX }, + winMouseY: { typeInfo: DataType.float1, get: p => p.winMouseY }, + pwinMouseX: { typeInfo: DataType.float1, get: p => p.pwinMouseX }, + pwinMouseY: { typeInfo: DataType.float1, get: p => p.pwinMouseY }, + frameCount: { typeInfo: DataType.float1, get: p => p.frameCount }, + deltaTime: { typeInfo: DataType.float1, get: p => p.deltaTime }, + displayWidth: { typeInfo: DataType.float1, get: p => p.displayWidth }, + displayHeight: { typeInfo: DataType.float1, get: p => p.displayHeight }, + windowWidth: { typeInfo: DataType.float1, get: p => p.windowWidth }, + windowHeight: { typeInfo: DataType.float1, get: p => p.windowHeight }, + mouseIsPressed: { typeInfo: DataType.bool1, get: p => p.mouseIsPressed } +}; function _getBuiltinGlobalsCache(strandsContext) { - if (!strandsContext._builtinGlobals || strandsContext._builtinGlobals.dag !== strandsContext.dag) { + if ( + !strandsContext._builtinGlobals || + strandsContext._builtinGlobals.dag !== strandsContext.dag + ) { strandsContext._builtinGlobals = { dag: strandsContext.dag, nodes: new Map(), - uniformsAdded: new Set(), - } + uniformsAdded: new Set() + }; } // return the cache - return strandsContext._builtinGlobals + return strandsContext._builtinGlobals; } -function getOrCreateUniformNode(strandsContext, uniformName, typeInfo, defaultValueFn) { +function getOrCreateUniformNode( + strandsContext, + uniformName, + typeInfo, + defaultValueFn +) { const cache = _getBuiltinGlobalsCache(strandsContext); const cached = cache.nodes.get(uniformName); @@ -66,11 +74,15 @@ function getOrCreateUniformNode(strandsContext, uniformName, typeInfo, defaultVa strandsContext.uniforms.push({ name: uniformName, typeInfo, - defaultValue: defaultValueFn, + defaultValue: defaultValueFn }); } - const { id, dimension } = build.variableNode(strandsContext, typeInfo, uniformName); + const { id, dimension } = build.variableNode( + strandsContext, + typeInfo, + uniformName + ); const node = createStrandsNode(id, dimension, strandsContext); cache.nodes.set(uniformName, node); return node; @@ -81,7 +93,8 @@ function getBuiltinGlobalNode(strandsContext, name) { if (!spec) return null; const uniformName = `_p5_global_${name}`; - const instance = strandsContext.renderer?._pInst || strandsContext.p5?.instance; + const instance = + strandsContext.renderer?._pInst || strandsContext.p5?.instance; const node = getOrCreateUniformNode( strandsContext, @@ -97,35 +110,39 @@ function getBuiltinGlobalNode(strandsContext, name) { } function installBuiltinGlobalAccessors(strandsContext) { - if (strandsContext._builtinGlobalsAccessorsInstalled) return + if (strandsContext._builtinGlobalsAccessorsInstalled) return; - const getRuntimeP5Instance = () => strandsContext.renderer?._pInst || strandsContext.p5?.instance + const getRuntimeP5Instance = () => + strandsContext.renderer?._pInst || strandsContext.p5?.instance; for (const name of Object.keys(BUILTIN_GLOBAL_SPECS)) { - const spec = BUILTIN_GLOBAL_SPECS[name] - const backingKey = `_strands_${name}` + const spec = BUILTIN_GLOBAL_SPECS[name]; + const backingKey = `_strands_${name}`; // Define on window for global mode only - const inst = getRuntimeP5Instance() + const inst = getRuntimeP5Instance(); if (inst?._isGlobal) { Object.defineProperty(window, name, { get: () => { if (strandsContext.active) { return getBuiltinGlobalNode(strandsContext, name); } - const inst = getRuntimeP5Instance() + const inst = getRuntimeP5Instance(); return spec.get(inst); }, - configurable: true, - }) + configurable: true + }); } // Capture original descriptor (held in closure for the getter to delegate to) - const originalProtoDesc = Object.getOwnPropertyDescriptor(strandsContext.p5.prototype, name); + const originalProtoDesc = Object.getOwnPropertyDescriptor( + strandsContext.p5.prototype, + name + ); // Define on p5.prototype for instance mode Object.defineProperty(strandsContext.p5.prototype, name, { - get: function() { + get: function () { if (strandsContext.active) { return getBuiltinGlobalNode(strandsContext, name); } @@ -140,11 +157,11 @@ function installBuiltinGlobalAccessors(strandsContext) { // Fall back to original value for data properties (like mouseX) return originalProtoDesc?.value; }, - set: function(val) { + set: function (val) { this[backingKey] = val; }, - configurable: true, - }) + configurable: true + }); // Define on p5.Graphics.prototype for graphics mode const GraphicsProto = strandsContext.p5?.Graphics?.prototype; @@ -152,7 +169,7 @@ function installBuiltinGlobalAccessors(strandsContext) { const originalDesc = Object.getOwnPropertyDescriptor(GraphicsProto, name); Object.defineProperty(GraphicsProto, name, { - get: function() { + get: function () { if (strandsContext.active) { return getBuiltinGlobalNode(strandsContext, name); } @@ -162,28 +179,33 @@ function installBuiltinGlobalAccessors(strandsContext) { } return this[backingKey]; }, - set: function(val) { + set: function (val) { if (originalDesc?.set) { originalDesc.set.call(this, val); } else { this[backingKey] = val; } }, - configurable: true, - }) + configurable: true + }); } } - strandsContext._builtinGlobalsAccessorsInstalled = true + strandsContext._builtinGlobalsAccessorsInstalled = true; } function installInstanceIndexAccessor(strandsContext) { if (strandsContext._instanceIndexAccessorInstalled) return; - const getRuntimeP5Instance = () => strandsContext.renderer?._pInst || strandsContext.p5?.instance; + const getRuntimeP5Instance = () => + strandsContext.renderer?._pInst || strandsContext.p5?.instance; - const instanceIndexGetter = function() { + const instanceIndexGetter = function () { if (strandsContext.active) { - const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, strandsContext.backend.instanceIdReference()); + const node = build.variableNode( + strandsContext, + { baseType: BaseType.INT, dimension: 1 }, + strandsContext.backend.instanceIdReference() + ); return createStrandsNode(node.id, node.dimension, strandsContext); } return undefined; @@ -193,20 +215,20 @@ function installInstanceIndexAccessor(strandsContext) { if (inst?._isGlobal) { Object.defineProperty(window, 'instanceIndex', { get: instanceIndexGetter, - configurable: true, + configurable: true }); } Object.defineProperty(strandsContext.p5.prototype, 'instanceIndex', { get: instanceIndexGetter, - configurable: true, + configurable: true }); const GraphicsProto = strandsContext.p5?.Graphics?.prototype; if (GraphicsProto) { Object.defineProperty(GraphicsProto, 'instanceIndex', { get: instanceIndexGetter, - configurable: true, + configurable: true }); } @@ -242,9 +264,10 @@ function augmentFnTemporary(fn, strandsContext, name, value) { fn[name] = value; const GraphicsProto = strandsContext.p5?.Graphics?.prototype; if (GraphicsProto) { - strandsContext.graphicsOverrides[name] = Object.prototype.hasOwnProperty.call(GraphicsProto, name) - ? GraphicsProto[name] - : undefined; + strandsContext.graphicsOverrides[name] = + Object.prototype.hasOwnProperty.call(GraphicsProto, name) + ? GraphicsProto[name] + : undefined; GraphicsProto[name] = value; } } @@ -258,51 +281,79 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { for (const { name, arity, opCode } of OperatorTable) { if (arity === 'binary') { StrandsNode.prototype[name] = function (...right) { - const { id, dimension } = build.binaryOpNode(strandsContext, this, right, opCode); + const { id, dimension } = build.binaryOpNode( + strandsContext, + this, + right, + opCode + ); return createStrandsNode(id, dimension, strandsContext); }; } if (arity === 'unary') { p5[name] = function (nodeOrValue) { - const { id, dimension } = build.unaryOpNode(strandsContext, nodeOrValue, opCode); + const { id, dimension } = build.unaryOpNode( + strandsContext, + nodeOrValue, + opCode + ); return createStrandsNode(id, dimension, strandsContext); - } + }; } } ////////////////////////////////////////////// // Unique Functions ////////////////////////////////////////////// - augmentFn(fn, p5, 'discard', function() { + augmentFn(fn, p5, 'discard', function () { build.statementNode(strandsContext, StatementType.DISCARD); }); - augmentFn(fn, p5, 'break', function() { + augmentFn(fn, p5, 'break', function () { build.statementNode(strandsContext, StatementType.BREAK); }); p5.break = fn.break; - augmentFn(fn, p5, 'instanceID', function() { - const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, strandsContext.backend.instanceIdReference()); + augmentFn(fn, p5, 'instanceID', function () { + const node = build.variableNode( + strandsContext, + { baseType: BaseType.INT, dimension: 1 }, + strandsContext.backend.instanceIdReference() + ); return createStrandsNode(node.id, node.dimension, strandsContext); }); // Internal methods use p5 static methods; user-facing methods use fn. // Some methods need to be used by both. - p5.strandsIf = function(conditionNode, ifBody) { + p5.strandsIf = function (conditionNode, ifBody) { return new StrandsConditional(strandsContext, conditionNode, ifBody); - } + }; augmentFn(fn, p5, 'strandsIf', p5.strandsIf); - p5.strandsFor = function(initialCb, conditionCb, updateCb, bodyCb, initialVars) { - return new StrandsFor(strandsContext, initialCb, conditionCb, updateCb, bodyCb, initialVars).build(); + p5.strandsFor = function ( + initialCb, + conditionCb, + updateCb, + bodyCb, + initialVars + ) { + return new StrandsFor( + strandsContext, + initialCb, + conditionCb, + updateCb, + bodyCb, + initialVars + ).build(); }; augmentFn(fn, p5, 'strandsFor', p5.strandsFor); - p5.strandsTernary = function(condition, ifTrue, ifFalse) { + p5.strandsTernary = function (condition, ifTrue, ifFalse) { return buildTernary(strandsContext, condition, ifTrue, ifFalse); }; augmentFn(fn, p5, 'strandsTernary', p5.strandsTernary); - p5.strandsEarlyReturn = function(value) { + p5.strandsEarlyReturn = function (value) { const { dag, cfg } = strandsContext; // Ensure we're inside a hook if (!strandsContext.activeHook) { - throw new Error('strandsEarlyReturn can only be used inside a hook callback'); + throw new Error( + 'strandsEarlyReturn can only be used inside a hook callback' + ); } // Convert value to a StrandsNode if it isn't already @@ -330,12 +381,15 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { return valueNode; }; augmentFn(fn, p5, 'strandsEarlyReturn', p5.strandsEarlyReturn); - p5.strandsNode = function(...args) { + p5.strandsNode = function (...args) { if (args.length === 1 && args[0] instanceof StrandsNode) { return args[0]; } if (args.length > 4) { - FES.userError("type error", "It looks like you've tried to construct a p5.strands node implicitly, with more than 4 components. This is currently not supported.") + FES.userError( + 'type error', + "It looks like you've tried to construct a p5.strands node implicitly, with more than 4 components. This is currently not supported." + ); } // Filter out undefined/null values const flatArgs = args.flat(); @@ -346,23 +400,37 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { // For that, we use an assign-on-use node, meaning we'll take the type of the // values assigned to it. if (definedArgs.length === 0) { - const { id, dimension } = build.primitiveConstructorNode(strandsContext, { baseType: BaseType.ASSIGN_ON_USE, dimension: null }, [0]); + const { id, dimension } = build.primitiveConstructorNode( + strandsContext, + { baseType: BaseType.ASSIGN_ON_USE, dimension: null }, + [0] + ); return createStrandsNode(id, dimension, strandsContext); } - const { id, dimension } = build.primitiveConstructorNode(strandsContext, { baseType: BaseType.FLOAT, dimension: null }, definedArgs); - return createStrandsNode(id, dimension, strandsContext);//new StrandsNode(id, dimension, strandsContext); - } + const { id, dimension } = build.primitiveConstructorNode( + strandsContext, + { baseType: BaseType.FLOAT, dimension: null }, + definedArgs + ); + return createStrandsNode(id, dimension, strandsContext); //new StrandsNode(id, dimension, strandsContext); + }; ////////////////////////////////////////////// // Builtins, uniforms, variable constructors ////////////////////////////////////////////// - for (const [functionName, overrides] of Object.entries(strandsBuiltinFunctions)) { + for (const [functionName, overrides] of Object.entries( + strandsBuiltinFunctions + )) { const isp5Function = overrides[0].isp5Function; if (isp5Function) { const originalFn = fn[functionName]; - augmentFn(fn, p5, functionName, function(...args) { + augmentFn(fn, p5, functionName, function (...args) { if (strandsContext.active) { - const { id, dimension } = build.functionCallNode(strandsContext, functionName, args); + const { id, dimension } = build.functionCallNode( + strandsContext, + functionName, + args + ); return createStrandsNode(id, dimension, strandsContext); } else { return originalFn.apply(this, args); @@ -371,10 +439,15 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { } else { augmentFn(fn, p5, functionName, function (...args) { if (strandsContext.active) { - const { id, dimension } = build.functionCallNode(strandsContext, functionName, args); + const { id, dimension } = build.functionCallNode( + strandsContext, + functionName, + args + ); return createStrandsNode(id, dimension, strandsContext); } else { - p5.FES.log`It looks like you've called ${functionName} outside of a shader's modify() function.`(); + p5.FES + .log`It looks like you've called ${functionName} outside of a shader's modify() function.`(); } }); } @@ -402,7 +475,11 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { const t = nNode.sub(start1Node).div(stop1Node.sub(start1Node)); const result = this.mix(start2, stop2, t); if (withinBounds) { - return this.clamp(result, this.min(start2, stop2), this.max(start2, stop2)); + return this.clamp( + result, + this.min(start2, stop2), + this.max(start2, stop2) + ); } return result; }); @@ -469,49 +546,53 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { // Using mix/step to avoid branching, via the compact form from: // http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl const _rgb2hsb = (instance, colorNode) => { - const r = colorNode.x; - const g = colorNode.y; - const b = colorNode.z; - const K = instance.vec4(0, -1/3, 2/3, -1); - const p = instance.mix( - instance.vec4(b, g, K.w, K.z), - instance.vec4(g, b, K.x, K.y), - instance.step(b, g) - ); - const q = instance.mix( - instance.vec4(p.x, p.y, p.w, r), - instance.vec4(r, p.y, p.z, p.x), - instance.step(p.x, r) - ); - const d = q.x.sub(instance.min(q.w, q.y)); - const e = p5.strandsNode(1e-10); - const h = instance.abs(q.z.add(q.w.sub(q.y).div(d.mult(6).add(e)))); - const s = d.div(q.x.add(e)); - return instance.vec3(h, s, q.x); -}; + const r = colorNode.x; + const g = colorNode.y; + const b = colorNode.z; + const K = instance.vec4(0, -1 / 3, 2 / 3, -1); + const p = instance.mix( + instance.vec4(b, g, K.w, K.z), + instance.vec4(g, b, K.x, K.y), + instance.step(b, g) + ); + const q = instance.mix( + instance.vec4(p.x, p.y, p.w, r), + instance.vec4(r, p.y, p.z, p.x), + instance.step(p.x, r) + ); + const d = q.x.sub(instance.min(q.w, q.y)); + const e = p5.strandsNode(1e-10); + const h = instance.abs(q.z.add(q.w.sub(q.y).div(d.mult(6).add(e)))); + const s = d.div(q.x.add(e)); + return instance.vec3(h, s, q.x); + }; -const _rgb2hsl = (instance, colorNode) => { - const r = colorNode.x; - const g = colorNode.y; - const b = colorNode.z; - const maxC = instance.max(r, instance.max(g, b)); - const minC = instance.min(r, instance.min(g, b)); - const l = maxC.add(minC).div(2); - const d = maxC.sub(minC); - const e = p5.strandsNode(1e-10); - const s = instance.mix( - p5.strandsNode(0), - d.div(p5.strandsNode(1).sub(instance.abs(l.mult(2).sub(1)))), - instance.step(e, d) - ); - const h_rg = instance.mod(g.sub(b).div(d.add(e)), p5.strandsNode(6)).div(6); - const h_gb = b.sub(r).div(d.add(e)).add(2).div(6); - const h_br = r.sub(g).div(d.add(e)).add(4).div(6); - const isR = instance.step(maxC.sub(e), r).mult(instance.step(r.sub(e), maxC)); - const isG = instance.step(maxC.sub(e), g).mult(instance.step(g.sub(e), maxC)); - const h = instance.mix(instance.mix(h_br, h_gb, isG), h_rg, isR); - return instance.vec3(h, s, l); -}; + const _rgb2hsl = (instance, colorNode) => { + const r = colorNode.x; + const g = colorNode.y; + const b = colorNode.z; + const maxC = instance.max(r, instance.max(g, b)); + const minC = instance.min(r, instance.min(g, b)); + const l = maxC.add(minC).div(2); + const d = maxC.sub(minC); + const e = p5.strandsNode(1e-10); + const s = instance.mix( + p5.strandsNode(0), + d.div(p5.strandsNode(1).sub(instance.abs(l.mult(2).sub(1)))), + instance.step(e, d) + ); + const h_rg = instance.mod(g.sub(b).div(d.add(e)), p5.strandsNode(6)).div(6); + const h_gb = b.sub(r).div(d.add(e)).add(2).div(6); + const h_br = r.sub(g).div(d.add(e)).add(4).div(6); + const isR = instance + .step(maxC.sub(e), r) + .mult(instance.step(r.sub(e), maxC)); + const isG = instance + .step(maxC.sub(e), g) + .mult(instance.step(g.sub(e), maxC)); + const h = instance.mix(instance.mix(h_br, h_gb, isG), h_rg, isR); + return instance.vec3(h, s, l); + }; const originalHue = fn.hue; augmentFn(fn, p5, 'hue', function (...args) { if (!strandsContext.active) return originalHue.apply(this, args); @@ -542,10 +623,14 @@ const _rgb2hsl = (instance, colorNode) => { augmentFn(fn, p5, 'getTexture', function (...rawArgs) { if (strandsContext.active) { - const { id, dimension } = strandsContext.backend.createGetTextureCall(strandsContext, rawArgs); + const { id, dimension } = strandsContext.backend.createGetTextureCall( + strandsContext, + rawArgs + ); return createStrandsNode(id, dimension, strandsContext); } else { - p5.FES.log`It looks like you've called getTexture outside of a shader's modify() function.`(); + p5.FES + .log`It looks like you've called getTexture outside of a shader's modify() function.`(); } }); @@ -563,7 +648,7 @@ const _rgb2hsl = (instance, colorNode) => { const originalNoise = fn.noise; const originalNoiseDetail = fn.noiseDetail; const originalRandom = fn.random; - const originalRandomGaussian=fn.randomGaussian; + const originalRandomGaussian = fn.randomGaussian; const originalRandomSeed = fn.randomSeed; const originalMillis = fn.millis; @@ -605,25 +690,35 @@ const _rgb2hsl = (instance, colorNode) => { nodeArgs = [fn.vec3(strandsArgs[0], 0, 0)]; } } else { - p5.FES.log`It looks like you've called noise() with ${args.length} arguments. It only supports 1D to 3D input.`(); + p5.FES + .log`It looks like you've called noise() with ${args.length} arguments. It only supports 1D to 3D input.`(); } - const octaves = strandsContext._noiseOctaves !== null - ? strandsContext._noiseOctaves - : fn._getNoiseOctaves(); - const falloff = strandsContext._noiseAmpFalloff !== null - ? strandsContext._noiseAmpFalloff - : fn._getNoiseAmpFalloff(); + const octaves = + strandsContext._noiseOctaves !== null + ? strandsContext._noiseOctaves + : fn._getNoiseOctaves(); + const falloff = + strandsContext._noiseAmpFalloff !== null + ? strandsContext._noiseAmpFalloff + : fn._getNoiseAmpFalloff(); nodeArgs.push(octaves); nodeArgs.push(falloff); - const { id, dimension } = build.functionCallNode(strandsContext, 'noise', nodeArgs, { - overloads: [{ - params: [DataType.float3, DataType.int1, DataType.float1], - returnType: DataType.float1, - }] - }); + const { id, dimension } = build.functionCallNode( + strandsContext, + 'noise', + nodeArgs, + { + overloads: [ + { + params: [DataType.float3, DataType.int1, DataType.float1], + returnType: DataType.float1 + } + ] + } + ); return createStrandsNode(id, dimension, strandsContext); }); @@ -641,19 +736,25 @@ const _rgb2hsl = (instance, colorNode) => { return originalRandom.apply(this, args); } - const randomVertSnippet = strandsContext.backend.getRandomVertexShaderSnippet(); - const randomFragSnippet = strandsContext.backend.getRandomFragmentShaderSnippet(); + const randomVertSnippet = + strandsContext.backend.getRandomVertexShaderSnippet(); + const randomFragSnippet = + strandsContext.backend.getRandomFragmentShaderSnippet(); strandsContext.vertexDeclarations.add(randomVertSnippet); strandsContext.fragmentDeclarations.add(randomFragSnippet); if (strandsContext.backend.getRandomComputeShaderSnippet) { - const randomComputeSnippet = strandsContext.backend.getRandomComputeShaderSnippet(); + const randomComputeSnippet = + strandsContext.backend.getRandomComputeShaderSnippet(); strandsContext.computeDeclarations.add(randomComputeSnippet); } let seedNode; - if (strandsContext._randomSeed !== null && strandsContext._randomSeed.isStrandsNode) { + if ( + strandsContext._randomSeed !== null && + strandsContext._randomSeed.isStrandsNode + ) { seedNode = strandsContext._randomSeed; } else { const userSeed = strandsContext._randomSeed; @@ -661,9 +762,7 @@ const _rgb2hsl = (instance, colorNode) => { strandsContext, '_p5_randomSeed', DataType.float1, - userSeed !== null - ? () => userSeed - : () => performance.now(), + userSeed !== null ? () => userSeed : () => performance.now() ); } @@ -671,57 +770,86 @@ const _rgb2hsl = (instance, colorNode) => { // single AST node still produces distinct values across runtime loop // iterations. We just pass the seed. const nodeArgs = [seedNode]; - const randomOverloads = [{ - params: [DataType.float1], - returnType: DataType.float1, - }]; + const randomOverloads = [ + { + params: [DataType.float1], + returnType: DataType.float1 + } + ]; if (args.length === 0) { - const { id, dimension } = build.functionCallNode(strandsContext, 'random', nodeArgs, { - overloads: randomOverloads, - }); + const { id, dimension } = build.functionCallNode( + strandsContext, + 'random', + nodeArgs, + { + overloads: randomOverloads + } + ); return createStrandsNode(id, dimension, strandsContext); } else if (args.length === 1) { // random(max) → [0, max) - const rawNode = build.functionCallNode(strandsContext, 'random', nodeArgs, { - overloads: randomOverloads, - }); - const rawStrandsNode = createStrandsNode(rawNode.id, rawNode.dimension, strandsContext); + const rawNode = build.functionCallNode( + strandsContext, + 'random', + nodeArgs, + { + overloads: randomOverloads + } + ); + const rawStrandsNode = createStrandsNode( + rawNode.id, + rawNode.dimension, + strandsContext + ); return rawStrandsNode.mult(p5.strandsNode(args[0])); } else if (args.length === 2) { // random(min, max) → [min, max) - const rawNode = build.functionCallNode(strandsContext, 'random', nodeArgs, { - overloads: randomOverloads, - }); - const rawStrandsNode = createStrandsNode(rawNode.id, rawNode.dimension, strandsContext); + const rawNode = build.functionCallNode( + strandsContext, + 'random', + nodeArgs, + { + overloads: randomOverloads + } + ); + const rawStrandsNode = createStrandsNode( + rawNode.id, + rawNode.dimension, + strandsContext + ); const minNode = p5.strandsNode(args[0]); const maxNode = p5.strandsNode(args[1]); // min + raw * (max - min) return rawStrandsNode.mult(maxNode.sub(minNode)).add(minNode); } else { - p5.FES.log`It looks like you've called random() with ${args.length} arguments. In strands, random() supports 0, 1, or 2 numeric arguments.`(); + p5.FES + .log`It looks like you've called random() with ${args.length} arguments. In strands, random() supports 0, 1, or 2 numeric arguments.`(); } }); - augmentFn(fn, p5, 'randomGaussian', function(...args){ - if(!strandsContext.active){ - return originalRandomGaussian.apply(this, args); - } - const mean = args.length >= 1 ? args[0] : 0; - const stdDev = args.length >= 2 ? args[1] : 1; + augmentFn(fn, p5, 'randomGaussian', function (...args) { + if (!strandsContext.active) { + return originalRandomGaussian.apply(this, args); + } + const mean = args.length >= 1 ? args[0] : 0; + const stdDev = args.length >= 2 ? args[1] : 1; - const u1 = this.max(this.random(), 1e-6); - const u2 = this.random(); - const z = this.sqrt(this.log(u1).mult(-2)).mult(this.cos(u2.mult(2*Math.PI))); + const u1 = this.max(this.random(), 1e-6); + const u2 = this.random(); + const z = this.sqrt(this.log(u1).mult(-2)).mult( + this.cos(u2.mult(2 * Math.PI)) + ); - return z.mult(stdDev).add(mean); - }); + return z.mult(stdDev).add(mean); + }); augmentFn(fn, p5, 'millis', function (...args) { if (!strandsContext.active) { return originalMillis.apply(this, args); } - const instance = strandsContext.renderer?._pInst || strandsContext.p5?.instance; + const instance = + strandsContext.renderer?._pInst || strandsContext.p5?.instance; return getOrCreateUniformNode( strandsContext, '_p5_global_millis', @@ -737,36 +865,51 @@ const _rgb2hsl = (instance, colorNode) => { // variant or also one more directly translated from GLSL, or to be more compatible with // APIs we documented at the release of 2.x and have to continue supporting. for (const type in DataType) { - if (type === BaseType.DEFER || type === BaseType.ASSIGN_ON_USE || type === 'sampler') { + if ( + type === BaseType.DEFER || + type === BaseType.ASSIGN_ON_USE || + type === 'sampler' + ) { continue; } const typeInfo = DataType[type]; const typeAliases = []; let pascalTypeName; if (/^[ib]vec/.test(typeInfo.fnName)) { - pascalTypeName = typeInfo.fnName - .slice(0, 2).toUpperCase() - + typeInfo.fnName - .slice(2) - .toLowerCase(); + pascalTypeName = + typeInfo.fnName.slice(0, 2).toUpperCase() + + typeInfo.fnName.slice(2).toLowerCase(); typeAliases.push(pascalTypeName.replace('Vec', 'Vector')); } else { - pascalTypeName = typeInfo.fnName.charAt(0).toUpperCase() - + typeInfo.fnName.slice(1); + pascalTypeName = + typeInfo.fnName.charAt(0).toUpperCase() + typeInfo.fnName.slice(1); if (pascalTypeName === 'Sampler2D') { - typeAliases.push('Texture') + typeAliases.push('Texture'); } else if (/^vec/.test(typeInfo.fnName)) { typeAliases.push(pascalTypeName.replace('Vec', 'Vector')); } } - augmentFn(fn, p5, `uniform${pascalTypeName}`, function(name, defaultValue) { - const { id, dimension } = build.variableNode(strandsContext, typeInfo, name); - strandsContext.uniforms.push({ name, typeInfo, defaultValue }); - return createStrandsNode(id, dimension, strandsContext); - }); + augmentFn( + fn, + p5, + `uniform${pascalTypeName}`, + function (name, defaultValue) { + const { id, dimension } = build.variableNode( + strandsContext, + typeInfo, + name + ); + strandsContext.uniforms.push({ name, typeInfo, defaultValue }); + return createStrandsNode(id, dimension, strandsContext); + } + ); // Shared variables with smart context detection - augmentFn(fn, p5, `shared${pascalTypeName}`, function(name) { - const { id, dimension } = build.variableNode(strandsContext, typeInfo, name); + augmentFn(fn, p5, `shared${pascalTypeName}`, function (name) { + const { id, dimension } = build.variableNode( + strandsContext, + typeInfo, + name + ); // Initialize shared variables tracking if not present if (!strandsContext.sharedVariables) { @@ -777,14 +920,19 @@ const _rgb2hsl = (instance, colorNode) => { strandsContext.sharedVariables.set(name, { typeInfo, usedInVertex: false, - usedInFragment: false, + usedInFragment: false }); return createStrandsNode(id, dimension, strandsContext); }); // Alias varying* as shared* for backward compatibility - augmentFn(fn, p5, `varying${pascalTypeName}`, fn[`shared${pascalTypeName}`]); + augmentFn( + fn, + p5, + `varying${pascalTypeName}`, + fn[`shared${pascalTypeName}`] + ); for (const typeAlias of typeAliases) { // For compatibility, also alias uniformVec2 as uniformVector2, what we initially @@ -794,41 +942,61 @@ const _rgb2hsl = (instance, colorNode) => { augmentFn(fn, p5, `shared${typeAlias}`, fn[`shared${pascalTypeName}`]); } const originalp5Fn = fn[typeInfo.fnName]; - augmentFn(fn, p5, typeInfo.fnName, function(...args) { + augmentFn(fn, p5, typeInfo.fnName, function (...args) { if (strandsContext.active) { - if (args.length === 1 && args[0].dimension && args[0].dimension === typeInfo.dimension) { + if ( + args.length === 1 && + args[0].dimension && + args[0].dimension === typeInfo.dimension + ) { const { id, dimension } = build.functionCallNode( strandsContext, - strandsContext.backend.getTypeName(typeInfo.baseType, typeInfo.dimension), + strandsContext.backend.getTypeName( + typeInfo.baseType, + typeInfo.dimension + ), args, { - overloads: [{ - params: [args[0].typeInfo()], - returnType: typeInfo, - }] + overloads: [ + { + params: [args[0].typeInfo()], + returnType: typeInfo + } + ] } ); return createStrandsNode(id, dimension, strandsContext); } else { // For vector types with a single argument, repeat it for each component - if (typeInfo.dimension > 1 && args.length === 1 && !Array.isArray(args[0]) && - !(args[0] instanceof StrandsNode && args[0].dimension > 1) && - (typeInfo.baseType === BaseType.FLOAT || typeInfo.baseType === BaseType.INT || typeInfo.baseType === BaseType.BOOL)) { + if ( + typeInfo.dimension > 1 && + args.length === 1 && + !Array.isArray(args[0]) && + !(args[0] instanceof StrandsNode && args[0].dimension > 1) && + (typeInfo.baseType === BaseType.FLOAT || + typeInfo.baseType === BaseType.INT || + typeInfo.baseType === BaseType.BOOL) + ) { args = Array(typeInfo.dimension).fill(args[0]); } - const { id, dimension } = build.primitiveConstructorNode(strandsContext, typeInfo, args); + const { id, dimension } = build.primitiveConstructorNode( + strandsContext, + typeInfo, + args + ); return createStrandsNode(id, dimension, strandsContext); } } else if (originalp5Fn) { return originalp5Fn.apply(this, args); } else { - p5.FES.log`It looks like you've called ${typeInfo.fnName} outside of a shader's modify() function.`() + p5.FES + .log`It looks like you've called ${typeInfo.fnName} outside of a shader's modify() function.`(); } }); } // Storage buffer uniform function for compute shaders - fn.uniformStorage = function(name, bufferOrSchema) { + fn.uniformStorage = function (name, bufferOrSchema) { let schema = null; let defaultValue = null; @@ -861,7 +1029,7 @@ const _rgb2hsl = (instance, colorNode) => { strandsContext.uniforms.push({ name, typeInfo: { baseType: 'storage', dimension: 1, schema }, - defaultValue, + defaultValue }); // Create StrandsNode with _originalIdentifier set (like varying variables) @@ -877,39 +1045,61 @@ const _rgb2hsl = (instance, colorNode) => { ////////////////////////////////////////////// // Per-Hook functions ////////////////////////////////////////////// -function createHookArguments(strandsContext, parameters){ +function createHookArguments(strandsContext, parameters) { const args = []; const dag = strandsContext.dag; for (const param of parameters) { - if(isStructType(param.type)) { + if (isStructType(param.type)) { const structTypeInfo = structType(param); - const { id, dimension } = build.structInstanceNode(strandsContext, structTypeInfo, `${HOOK_PARAM_PREFIX}${param.name}`, []); - const structNode = createStrandsNode(id, dimension, strandsContext).withStructProperties( - structTypeInfo.properties.map(prop => prop.name) + const { id, dimension } = build.structInstanceNode( + strandsContext, + structTypeInfo, + `${HOOK_PARAM_PREFIX}${param.name}`, + [] ); + const structNode = createStrandsNode( + id, + dimension, + strandsContext + ).withStructProperties(structTypeInfo.properties.map(prop => prop.name)); for (let i = 0; i < structTypeInfo.properties.length; i++) { const propertyType = structTypeInfo.properties[i]; Object.defineProperty(structNode, propertyType.name, { get() { - const propNode = getNodeDataFromID(dag, dag.dependsOn[structNode.id][i]) - const onRebind = (newFieldID) => { + const propNode = getNodeDataFromID( + dag, + dag.dependsOn[structNode.id][i] + ); + const onRebind = newFieldID => { const oldDeps = dag.dependsOn[structNode.id]; const newDeps = oldDeps.slice(); newDeps[i] = newFieldID; - const rebuilt = build.structInstanceNode(strandsContext, structTypeInfo, `${HOOK_PARAM_PREFIX}${param.name}`, newDeps); + const rebuilt = build.structInstanceNode( + strandsContext, + structTypeInfo, + `${HOOK_PARAM_PREFIX}${param.name}`, + newDeps + ); structNode.id = rebuilt.id; }; // TODO: implement member access operations // const { id, components } = createMemberAccessNode(strandsContext, structNode, componentNodes[i], componentTypeInfo.dataType); // const memberAccessNode = new StrandsNode(id, components); // return memberAccessNode; - return createStrandsNode(propNode.id, propNode.dimension, strandsContext, onRebind); + return createStrandsNode( + propNode.id, + propNode.dimension, + strandsContext, + onRebind + ); }, set(val) { const valDim = val?.isStrandsNode ? val.dimension - : (Array.isArray(val) ? val.length : 1); - if( valDim !== propertyType.dataType.dimension && valDim !== 1){ + : Array.isArray(val) + ? val.length + : 1; + if (valDim !== propertyType.dataType.dimension && valDim !== 1) { FES.dimensionMismatchError( propertyType.dataType.dimension, valDim, @@ -922,109 +1112,152 @@ function createHookArguments(strandsContext, parameters){ let newValueID; if (val?.isStrandsNode) { newValueID = val.id; - } - else { - let newVal = build.primitiveConstructorNode(strandsContext, propertyType.dataType, val); + } else { + let newVal = build.primitiveConstructorNode( + strandsContext, + propertyType.dataType, + val + ); newValueID = newVal.id; } newDependsOn[i] = newValueID; - const newStructInfo = build.structInstanceNode(strandsContext, structTypeInfo, `${HOOK_PARAM_PREFIX}${param.name}`, newDependsOn); + const newStructInfo = build.structInstanceNode( + strandsContext, + structTypeInfo, + `${HOOK_PARAM_PREFIX}${param.name}`, + newDependsOn + ); structNode.id = newStructInfo.id; } - }) + }); } args.push(structNode); - } - else /*if(isNativeType(paramType.typeName))*/ { + } else /*if(isNativeType(paramType.typeName))*/ { // Skip sampler parameters - they don't need strands nodes if (param.type.typeName === 'sampler') { continue; } if (!param.type.dataType) { - throw new Error(`Missing dataType for parameter ${param.name} of type ${param.type.typeName}`); + throw new Error( + `Missing dataType for parameter ${param.name} of type ${param.type.typeName}` + ); } const typeInfo = param.type.dataType; - const { id, dimension } = build.variableNode(strandsContext, typeInfo, `${HOOK_PARAM_PREFIX}${param.name}`); + const { id, dimension } = build.variableNode( + strandsContext, + typeInfo, + `${HOOK_PARAM_PREFIX}${param.name}` + ); const arg = createStrandsNode(id, dimension, strandsContext); args.push(arg); } } return args; } -function enforceReturnTypeMatch(strandsContext, expectedType, returned, hookName) { - if (!(returned?.isStrandsNode)) { +function enforceReturnTypeMatch( + strandsContext, + expectedType, + returned, + hookName +) { + if (!returned?.isStrandsNode) { // try { - const result = build.primitiveConstructorNode(strandsContext, expectedType, returned); - return result.id; + const result = build.primitiveConstructorNode( + strandsContext, + expectedType, + returned + ); + return result.id; // } catch (e) { - // FES.userError('type error', - // `There was a type mismatch for a value returned from ${hookName}.\n` + - // `The value in question was supposed to be:\n` + - // `${expectedType.baseType + expectedType.dimension}\n` + - // `But you returned:\n` + - // `${returned}` - // ); + // FES.userError('type error', + // `There was a type mismatch for a value returned from ${hookName}.\n` + + // `The value in question was supposed to be:\n` + + // `${expectedType.baseType + expectedType.dimension}\n` + + // `But you returned:\n` + + // `${returned}` + // ); // } } const dag = strandsContext.dag; let returnedNodeID = returned.id; const receivedType = { baseType: dag.baseTypes[returnedNodeID], - dimension: dag.dimensions[returnedNodeID], - } + dimension: dag.dimensions[returnedNodeID] + }; if (receivedType.dimension !== expectedType.dimension) { if (receivedType.dimension !== 1) { - const receivedTypeDisplay = receivedType.baseType + (receivedType.dimension > 1 ? receivedType.dimension : ''); - const expectedTypeDisplay = expectedType.baseType + expectedType.dimension; - FES.userError('type error', + const receivedTypeDisplay = + receivedType.baseType + + (receivedType.dimension > 1 ? receivedType.dimension : ''); + const expectedTypeDisplay = + expectedType.baseType + expectedType.dimension; + FES.userError( + 'type error', `You have returned a ${receivedTypeDisplay} in ${hookName} when a ${expectedTypeDisplay} was expected!\n\n` + - `Make sure your hook returns the correct type.` + `Make sure your hook returns the correct type.` + ); + } else { + const result = build.primitiveConstructorNode( + strandsContext, + expectedType, + returned ); - } - else { - const result = build.primitiveConstructorNode(strandsContext, expectedType, returned); returnedNodeID = result.id; } - } - else if (receivedType.baseType !== expectedType.baseType) { - const result = build.primitiveConstructorNode(strandsContext, expectedType, returned); + } else if (receivedType.baseType !== expectedType.baseType) { + const result = build.primitiveConstructorNode( + strandsContext, + expectedType, + returned + ); returnedNodeID = result.id; } return returnedNodeID; } export function createShaderHooksFunctions(strandsContext, fn, shader) { - installBuiltinGlobalAccessors(strandsContext) - installInstanceIndexAccessor(strandsContext) + installBuiltinGlobalAccessors(strandsContext); + installInstanceIndexAccessor(strandsContext); // Add shader context to hooks before spreading const vertexHooksWithContext = Object.fromEntries( - Object.entries(shader.hooks.vertex).map(([name, hook]) => [name, { ...hook, shaderContext: 'vertex' }]) + Object.entries(shader.hooks.vertex).map(([name, hook]) => [ + name, + { ...hook, shaderContext: 'vertex' } + ]) ); const fragmentHooksWithContext = Object.fromEntries( - Object.entries(shader.hooks.fragment).map(([name, hook]) => [name, { ...hook, shaderContext: 'fragment' }]) + Object.entries(shader.hooks.fragment).map(([name, hook]) => [ + name, + { ...hook, shaderContext: 'fragment' } + ]) ); const computeHooksWithContext = Object.fromEntries( - Object.entries(shader.hooks.compute).map(([name, hook]) => [name, { ...hook, shaderContext: 'compute' }]) + Object.entries(shader.hooks.compute).map(([name, hook]) => [ + name, + { ...hook, shaderContext: 'compute' } + ]) ); const availableHooks = { ...vertexHooksWithContext, ...fragmentHooksWithContext, - ...computeHooksWithContext, - } - const hookTypes = Object.keys(availableHooks).map(name => shader.hookTypes(name)); + ...computeHooksWithContext + }; + const hookTypes = Object.keys(availableHooks).map(name => + shader.hookTypes(name) + ); const { cfg, dag } = strandsContext; for (const hookType of hookTypes) { - const hook = function(hookUserCallback) { + const hook = function (hookUserCallback) { const args = setupHook(); hook._result = hookUserCallback(...args) ?? hook._result; finishHook(); - } + }; // In the flat strands API, this is how result-returning hooks // are used - hook.set = function(result) { + hook.set = function (result) { hook._result = result; }; hook._active = false; @@ -1053,10 +1286,10 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) { FES.userError( 'scope error', `It looks like you're trying to access "${displayName}.${key}" outside of its begin()/end() block.\n\n` + - `Properties of ${displayName} are only available between ` + - `${displayName}.begin() and ${displayName}.end().\n\n` + - `To share data between hooks, use sharedVec3() or sharedFloat() ` + - `to pass values between them.` + `Properties of ${displayName} are only available between ` + + `${displayName}.begin() and ${displayName}.end().\n\n` + + `To share data between hooks, use sharedVec3() or sharedFloat() ` + + `to pass values between them.` ); } return this._args[this._argIdx][key]; @@ -1070,7 +1303,7 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) { } this._args[this._argIdx][key] = val; }, - enumerable: true, + enumerable: true }); } } @@ -1092,7 +1325,10 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) { for (const key of args[argIdx].structProperties || []) { hook._properties.push(key); } - if (hookType.returnType?.typeName === hookType.parameters[argIdx].type.typeName) { + if ( + hookType.returnType?.typeName === + hookType.parameters[argIdx].type.typeName + ) { hook.set(args[argIdx]); } } else { @@ -1101,7 +1337,7 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) { } } return args; - }; + } function finishHook() { hook._active = false; @@ -1110,41 +1346,56 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) { const expectedReturnType = hookType.returnType; let rootNodeID = null; - const handleRetVal = (retNode) => { - if(isStructType(expectedReturnType)) { + const handleRetVal = retNode => { + if (isStructType(expectedReturnType)) { const expectedStructType = structType(expectedReturnType); if (retNode?.isStrandsNode) { - const returnedNode = getNodeDataFromID(strandsContext.dag, retNode.id); + const returnedNode = getNodeDataFromID( + strandsContext.dag, + retNode.id + ); if (returnedNode.baseType !== expectedStructType.typeName) { const receivedTypeName = returnedNode.baseType || 'undefined'; const receivedDim = dag.dimensions[retNode.id]; - const receivedTypeDisplay = receivedDim > 1 ? - `${receivedTypeName}${receivedDim}` : receivedTypeName; + const receivedTypeDisplay = + receivedDim > 1 + ? `${receivedTypeName}${receivedDim}` + : receivedTypeName; const expectedProps = expectedStructType.properties - .map(p => p.name).join(', '); - FES.userError('type error', + .map(p => p.name) + .join(', '); + FES.userError( + 'type error', `You have returned a ${receivedTypeDisplay} from ${hookType.name} when a ${expectedStructType.typeName} was expected.\n\n` + - `The ${expectedStructType.typeName} struct has these properties: { ${expectedProps} }\n\n` + - `Instead of returning a different type, you should modify and return the ${expectedStructType.typeName} struct that was passed to your hook.\n\n` + - `For example:\n` + - `${hookType.name}((inputs) => {\n` + - ` // Modify properties of inputs\n` + - ` inputs.someProperty = ...;\n` + - ` return inputs; // Return the modified struct\n` + - `})` + `The ${expectedStructType.typeName} struct has these properties: { ${expectedProps} }\n\n` + + `Instead of returning a different type, you should modify and return the ${expectedStructType.typeName} struct that was passed to your hook.\n\n` + + `For example:\n` + + `${hookType.name}((inputs) => {\n` + + ` // Modify properties of inputs\n` + + ` inputs.someProperty = ...;\n` + + ` return inputs; // Return the modified struct\n` + + `})` ); } const newDeps = returnedNode.dependsOn.slice(); for (let i = 0; i < expectedStructType.properties.length; i++) { const expectedType = expectedStructType.properties[i].dataType; - const receivedNode = createStrandsNode(returnedNode.dependsOn[i], dag.dependsOn[retNode.id], strandsContext); - newDeps[i] = enforceReturnTypeMatch(strandsContext, expectedType, receivedNode, hookType.name); + const receivedNode = createStrandsNode( + returnedNode.dependsOn[i], + dag.dependsOn[retNode.id], + strandsContext + ); + newDeps[i] = enforceReturnTypeMatch( + strandsContext, + expectedType, + receivedNode, + hookType.name + ); } dag.dependsOn[retNode.id] = newDeps; return retNode.id; - } - else { + } else { const expectedProperties = expectedStructType.properties; const newStructDependencies = []; for (let i = 0; i < expectedProperties.length; i++) { @@ -1152,31 +1403,49 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) { const propName = expectedProp.name; const receivedValue = retNode[propName]; if (receivedValue === undefined) { - const expectedProps = expectedReturnType.properties.map(p => p.name).join(', '); + const expectedProps = expectedReturnType.properties + .map(p => p.name) + .join(', '); const receivedProps = Object.keys(retNode).join(', '); - FES.userError('type error', + FES.userError( + 'type error', `You've returned an incomplete ${expectedStructType.typeName} struct from ${hookType.name}.\n\n` + - `Expected properties: { ${expectedProps} }\n` + - `Received properties: { ${receivedProps} }\n\n` + - `All properties are required! Make sure to include all properties in the returned struct.` + `Expected properties: { ${expectedProps} }\n` + + `Received properties: { ${receivedProps} }\n\n` + + `All properties are required! Make sure to include all properties in the returned struct.` ); } const expectedTypeInfo = expectedProp.dataType; - const returnedPropID = enforceReturnTypeMatch(strandsContext, expectedTypeInfo, receivedValue, hookType.name); + const returnedPropID = enforceReturnTypeMatch( + strandsContext, + expectedTypeInfo, + receivedValue, + hookType.name + ); newStructDependencies.push(returnedPropID); } - const newStruct = build.structConstructorNode(strandsContext, expectedStructType, newStructDependencies); + const newStruct = build.structConstructorNode( + strandsContext, + expectedStructType, + newStructDependencies + ); return newStruct.id; } - } - else if (!expectedReturnType.dataType || expectedReturnType.typeName?.trim() === 'void') { + } else if ( + !expectedReturnType.dataType || + expectedReturnType.typeName?.trim() === 'void' + ) { return null; - } - else /*if(isNativeType(expectedReturnType.typeName))*/ { + } else /*if(isNativeType(expectedReturnType.typeName))*/ { const expectedTypeInfo = expectedReturnType.dataType; - return enforceReturnTypeMatch(strandsContext, expectedTypeInfo, retNode, hookType.name); + return enforceReturnTypeMatch( + strandsContext, + expectedTypeInfo, + retNode, + hookType.name + ); } - } + }; for (const { valueNode, earlyReturnID } of hook.earlyReturns) { const id = handleRetVal(valueNode); if (id !== null) { @@ -1192,23 +1461,26 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) { hookType, entryBlockID, rootNodeID, - shaderContext: hookInfo?.shaderContext, // 'vertex', 'fragment', or 'compute' + shaderContext: hookInfo?.shaderContext // 'vertex', 'fragment', or 'compute' }); CFG.popBlock(cfg); - }; + } hook.begin = setupHook; hook.end = finishHook; const aliases = [hookType.name]; if (strandsContext.baseShader?.hooks?.hookAliases?.[hookType.name]) { - aliases.push(...strandsContext.baseShader.hooks.hookAliases[hookType.name]); + aliases.push( + ...strandsContext.baseShader.hooks.hookAliases[hookType.name] + ); } // If the hook has a name like getPixelInputs, create an alias without // the get* prefix, like pixelInputs const nameMatch = /^get([A-Z0-9]\w*)$/.exec(hookType.name); if (nameMatch) { - const unprefixedName = nameMatch[1][0].toLowerCase() + nameMatch[1].slice(1); + const unprefixedName = + nameMatch[1][0].toLowerCase() + nameMatch[1].slice(1); if (!fn[unprefixedName]) { aliases.push(unprefixedName); } diff --git a/src/strands/strands_builtins.js b/src/strands/strands_builtins.js index d6080ea428..a6347de73c 100644 --- a/src/strands/strands_builtins.js +++ b/src/strands/strands_builtins.js @@ -1,112 +1,334 @@ // Need the .js extension because we also import this from a Node script. // Try to keep this file minimal because of that. -import { GenType, DataType } from "./ir_types.js" +import { GenType, DataType } from './ir_types.js'; // GLSL Built in functions // https://docs.gl/el3/abs const builtInGLSLFunctions = { //////////// Trigonometry ////////// - acos: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - acosh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - asin: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - asinh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], + acos: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + acosh: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], + asin: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + asinh: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], atan: [ - { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - ], - atanh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - cos: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - cosh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - degrees: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - radians: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - sin: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT , isp5Function: true}], - sinh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - tan: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - tanh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }, + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: true + } + ], + atanh: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], + cos: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + cosh: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], + degrees: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + radians: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + sin: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + sinh: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], + tan: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + tanh: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], ////////// Mathematics ////////// abs: [ - { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.FLOAT], returnType: GenType.INT, isp5Function: true} + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }, + { params: [GenType.FLOAT], returnType: GenType.INT, isp5Function: true } + ], + ceil: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } ], - ceil: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], clamp: [ - { params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}, - { params: [GenType.FLOAT,DataType.float1,DataType.float1], returnType: GenType.FLOAT, isp5Function: false}, - { params: [GenType.INT, GenType.INT, GenType.INT], returnType: GenType.INT, isp5Function: false}, - { params: [GenType.INT, DataType.int1, DataType.int1], returnType: GenType.INT, isp5Function: false}, - ], - dFdx: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - dFdy: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - exp: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - exp2: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - floor: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - fma: [{ params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - fract: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - fwidth: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - inversesqrt: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], + { + params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: false + }, + { + params: [GenType.FLOAT, DataType.float1, DataType.float1], + returnType: GenType.FLOAT, + isp5Function: false + }, + { + params: [GenType.INT, GenType.INT, GenType.INT], + returnType: GenType.INT, + isp5Function: false + }, + { + params: [GenType.INT, DataType.int1, DataType.int1], + returnType: GenType.INT, + isp5Function: false + } + ], + dFdx: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], + dFdy: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], + exp: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + exp2: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], + floor: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + fma: [ + { + params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: false + } + ], + fract: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + fwidth: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], + inversesqrt: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], // "isinf": [{}], // "isnan": [{}], - log: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - log2: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], + log: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + log2: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], max: [ - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.INT, GenType.INT], returnType: GenType.INT, isp5Function: true}, - { params: [GenType.INT, DataType.int1], returnType: GenType.INT, isp5Function: true}, + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: true + }, + { + params: [GenType.FLOAT, DataType.float1], + returnType: GenType.FLOAT, + isp5Function: true + }, + { + params: [GenType.INT, GenType.INT], + returnType: GenType.INT, + isp5Function: true + }, + { + params: [GenType.INT, DataType.int1], + returnType: GenType.INT, + isp5Function: true + } ], min: [ - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.INT, GenType.INT], returnType: GenType.INT, isp5Function: true}, - { params: [GenType.INT, DataType.int1], returnType: GenType.INT, isp5Function: true}, + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: true + }, + { + params: [GenType.FLOAT, DataType.float1], + returnType: GenType.FLOAT, + isp5Function: true + }, + { + params: [GenType.INT, GenType.INT], + returnType: GenType.INT, + isp5Function: true + }, + { + params: [GenType.INT, DataType.int1], + returnType: GenType.INT, + isp5Function: true + } ], mix: [ - { params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}, - { params: [GenType.FLOAT, GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: false}, - { params: [GenType.FLOAT, GenType.FLOAT, GenType.BOOL], returnType: GenType.FLOAT, isp5Function: false}, + { + params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: false + }, + { + params: [GenType.FLOAT, GenType.FLOAT, DataType.float1], + returnType: GenType.FLOAT, + isp5Function: false + }, + { + params: [GenType.FLOAT, GenType.FLOAT, GenType.BOOL], + returnType: GenType.FLOAT, + isp5Function: false + } ], mod: [ - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: true}, + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: true + }, + { + params: [GenType.FLOAT, DataType.float1], + returnType: GenType.FLOAT, + isp5Function: true + } ], // "modf": [{}], - pow: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - round: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - roundEven: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], + pow: [ + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: true + } + ], + round: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + roundEven: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } + ], sign: [ - { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}, - { params: [GenType.INT], returnType: GenType.INT, isp5Function: false}, + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }, + { params: [GenType.INT], returnType: GenType.INT, isp5Function: false } ], smoothstep: [ - { params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}, - { params: [ DataType.float1,DataType.float1, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}, + { + params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: false + }, + { + params: [DataType.float1, DataType.float1, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: false + } + ], + sqrt: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } + ], + step: [ + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: false + } + ], + trunc: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } ], - sqrt: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - step: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - trunc: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], ////////// Vector ////////// - cross: [{ params: [DataType.float3, DataType.float3], returnType: DataType.float3, isp5Function: true}], - distance: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType:DataType.float1, isp5Function: true}], - dot: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType:DataType.float1, isp5Function: true}], + cross: [ + { + params: [DataType.float3, DataType.float3], + returnType: DataType.float3, + isp5Function: true + } + ], + distance: [ + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: DataType.float1, + isp5Function: true + } + ], + dot: [ + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: DataType.float1, + isp5Function: true + } + ], equal: [ - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.BOOL, isp5Function: false}, - { params: [GenType.INT, GenType.INT], returnType: GenType.BOOL, isp5Function: false}, - { params: [GenType.BOOL, GenType.BOOL], returnType: GenType.BOOL, isp5Function: false}, + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: GenType.BOOL, + isp5Function: false + }, + { + params: [GenType.INT, GenType.INT], + returnType: GenType.BOOL, + isp5Function: false + }, + { + params: [GenType.BOOL, GenType.BOOL], + returnType: GenType.BOOL, + isp5Function: false + } + ], + faceforward: [ + { + params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: false + } + ], + length: [ + { + params: [GenType.FLOAT], + returnType: DataType.float1, + isp5Function: false + } + ], + normalize: [ + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } ], - faceforward: [{ params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - length: [{ params: [GenType.FLOAT], returnType:DataType.float1, isp5Function: false}], - normalize: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], notEqual: [ - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.BOOL, isp5Function: false}, - { params: [GenType.INT, GenType.INT], returnType: GenType.BOOL, isp5Function: false}, - { params: [GenType.BOOL, GenType.BOOL], returnType: GenType.BOOL, isp5Function: false}, + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: GenType.BOOL, + isp5Function: false + }, + { + params: [GenType.INT, GenType.INT], + returnType: GenType.BOOL, + isp5Function: false + }, + { + params: [GenType.BOOL, GenType.BOOL], + returnType: GenType.BOOL, + isp5Function: false + } + ], + reflect: [ + { + params: [GenType.FLOAT, GenType.FLOAT], + returnType: GenType.FLOAT, + isp5Function: false + } ], - reflect: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - refract: [{ params: [GenType.FLOAT, GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: false}], -} + refract: [ + { + params: [GenType.FLOAT, GenType.FLOAT, DataType.float1], + returnType: GenType.FLOAT, + isp5Function: false + } + ] +}; export const strandsBuiltinFunctions = { - ...builtInGLSLFunctions, -} + ...builtInGLSLFunctions +}; diff --git a/src/strands/strands_codegen.js b/src/strands/strands_codegen.js index 38e24c511e..94fde28887 100644 --- a/src/strands/strands_codegen.js +++ b/src/strands/strands_codegen.js @@ -1,6 +1,11 @@ import { sortCFG } from './ir_cfg'; import * as DAG from './ir_dag'; -import { NodeType, StatementType, structType, TypeInfoFromGLSLName } from './ir_types'; +import { + NodeType, + StatementType, + structType, + TypeInfoFromGLSLName +} from './ir_types'; export function generateShaderCode(strandsContext) { const { @@ -14,10 +19,10 @@ export function generateShaderCode(strandsContext) { const hooksObj = { uniforms: {}, storageUniforms: {}, - varyingVariables: [], + varyingVariables: [] }; - for (const {name, typeInfo, defaultValue} of strandsContext.uniforms) { + for (const { name, typeInfo, defaultValue } of strandsContext.uniforms) { if (typeInfo.baseType === 'storage') { if (defaultValue !== null && defaultValue !== undefined) { hooksObj.storageUniforms[name] = defaultValue; @@ -40,7 +45,12 @@ export function generateShaderCode(strandsContext) { backend.addStorageBufferBindingsToDeclarations(strandsContext); } - for (const { hookType, rootNodeID, entryBlockID, shaderContext } of strandsContext.hooks) { + for (const { + hookType, + rootNodeID, + entryBlockID, + shaderContext + } of strandsContext.hooks) { const generationContext = { indent: 1, codeLines: [], @@ -52,7 +62,7 @@ export function generateShaderCode(strandsContext) { nextTempID: 0, visitedNodes: new Set(), shaderContext, // 'vertex' or 'fragment' - strandsContext, // For shared variable tracking + strandsContext // For shared variable tracking }; const blocks = sortCFG(cfg.outgoingEdges, entryBlockID); @@ -64,16 +74,28 @@ export function generateShaderCode(strandsContext) { let returnType; if (hookType.returnType.properties) { returnType = structType(hookType.returnType); - } else if (!hookType.returnType.dataType || hookType.returnType.typeName?.trim() === 'void') { + } else if ( + !hookType.returnType.dataType || + hookType.returnType.typeName?.trim() === 'void' + ) { returnType = null; } else { returnType = hookType.returnType.dataType; } if (rootNodeID !== undefined) { - backend.generateReturnStatement(strandsContext, generationContext, rootNodeID, returnType); + backend.generateReturnStatement( + strandsContext, + generationContext, + rootNodeID, + returnType + ); } - hooksObj[`${hookType.returnType.typeName} ${hookType.name}`] = [firstLine, ...generationContext.codeLines, '}'].join('\n'); + hooksObj[`${hookType.returnType.typeName} ${hookType.name}`] = [ + firstLine, + ...generationContext.codeLines, + '}' + ].join('\n'); } // Finalize shared variable declarations based on usage @@ -81,13 +103,19 @@ export function generateShaderCode(strandsContext) { for (const [varName, varInfo] of strandsContext.sharedVariables) { if (varInfo.usedInVertex && varInfo.usedInFragment) { // Used in both shaders - this is a true varying variable - hooksObj.varyingVariables.push(backend.generateVaryingVariable(varName, varInfo.typeInfo)); + hooksObj.varyingVariables.push( + backend.generateVaryingVariable(varName, varInfo.typeInfo) + ); } else if (varInfo.usedInVertex) { // Only used in vertex shader - declare as local variable - vertexDeclarations.add(backend.generateLocalDeclaration(varName, varInfo.typeInfo)); + vertexDeclarations.add( + backend.generateLocalDeclaration(varName, varInfo.typeInfo) + ); } else if (varInfo.usedInFragment) { // Only used in fragment shader - declare as local variable - fragmentDeclarations.add(backend.generateLocalDeclaration(varName, varInfo.typeInfo)); + fragmentDeclarations.add( + backend.generateLocalDeclaration(varName, varInfo.typeInfo) + ); } // If not used anywhere, don't declare it } diff --git a/src/strands/strands_conditionals.js b/src/strands/strands_conditionals.js index e9e6aca08e..cccd5af144 100644 --- a/src/strands/strands_conditionals.js +++ b/src/strands/strands_conditionals.js @@ -6,11 +6,13 @@ import { createPhiNode } from './strands_phi_utils'; export class StrandsConditional { constructor(strandsContext, condition, branchCallback) { // Condition must be a node... - this.branches = [{ - condition, - branchCallback, - blockType: BlockType.IF_BODY - }]; + this.branches = [ + { + condition, + branchCallback, + blockType: BlockType.IF_BODY + } + ]; this.ctx = strandsContext; } ElseIf(condition, branchCallback) { @@ -30,7 +32,11 @@ export class StrandsConditional { const phiNodes = buildConditional(this.ctx, this); const assignments = {}; for (const [varName, phiNode] of Object.entries(phiNodes)) { - assignments[varName] = createStrandsNode(phiNode.id, phiNode.dimension, this.ctx); + assignments[varName] = createStrandsNode( + phiNode.id, + phiNode.dimension, + this.ctx + ); } return assignments; } @@ -75,9 +81,14 @@ function buildConditional(strandsContext, conditional) { } for (const key in branchResults) { if (!phiBlockDependencies[key]) { - phiBlockDependencies[key] = [{ value: branchResults[key], blockId: branchContentBlock }]; + phiBlockDependencies[key] = [ + { value: branchResults[key], blockId: branchContentBlock } + ]; } else { - phiBlockDependencies[key].push({ value: branchResults[key], blockId: branchContentBlock }); + phiBlockDependencies[key].push({ + value: branchResults[key], + blockId: branchContentBlock + }); } } results.push(branchResults); @@ -96,7 +107,11 @@ function buildConditional(strandsContext, conditional) { // Push the branch block for modification to avoid changing the ordering CFG.pushBlockForModification(cfg, branchBlock); for (const key in phiBlockDependencies) { - mergedAssignments[key] = createPhiNode(strandsContext, phiBlockDependencies[key], key); + mergedAssignments[key] = createPhiNode( + strandsContext, + phiBlockDependencies[key], + key + ); } CFG.popBlock(cfg); for (let i = 0; i < results.length; i++) { @@ -116,7 +131,10 @@ function buildConditional(strandsContext, conditional) { dependsOn: [phiNodeID, sourceNodeID], phiBlocks: [] }; - const assignmentID = DAG.getOrCreateNode(strandsContext.dag, assignmentNode); + const assignmentID = DAG.getOrCreateNode( + strandsContext.dag, + assignmentNode + ); CFG.recordInBasicBlock(cfg, branchEndBlockID, assignmentID); } } diff --git a/src/strands/strands_for.js b/src/strands/strands_for.js index 22377d45a1..d035395201 100644 --- a/src/strands/strands_for.js +++ b/src/strands/strands_for.js @@ -1,12 +1,25 @@ import * as CFG from './ir_cfg'; import * as DAG from './ir_dag'; -import { BlockType, NodeType, BaseType, StatementType, OpCode } from './ir_types'; +import { + BlockType, + NodeType, + BaseType, + StatementType, + OpCode +} from './ir_types'; import { StrandsNode, createStrandsNode } from './strands_node'; import { primitiveConstructorNode } from './ir_builders'; import { createPhiNode } from './strands_phi_utils'; export class StrandsFor { - constructor(strandsContext, initialCb, conditionCb, updateCb, bodyCb, initialVars) { + constructor( + strandsContext, + initialCb, + conditionCb, + updateCb, + bodyCb, + initialVars + ) { this.strandsContext = strandsContext; this.initialCb = initialCb; this.conditionCb = conditionCb; @@ -25,22 +38,49 @@ export class StrandsFor { CFG.addEdge(cfg, branchBlock, mergeBlock); // Initialize loop variable phi node - const { initialVar, phiNode } = this.initializeLoopVariable(cfg, branchBlock); + const { initialVar, phiNode } = this.initializeLoopVariable( + cfg, + branchBlock + ); // Execute condition and update callbacks to get nodes for analysis CFG.pushBlock(cfg, cfg.currentBlock); - const loopVarNode = createStrandsNode(phiNode.id, phiNode.dimension, this.strandsContext); + const loopVarNode = createStrandsNode( + phiNode.id, + phiNode.dimension, + this.strandsContext + ); const conditionNode = this.conditionCb(loopVarNode); const updateResult = this.updateCb(loopVarNode); CFG.popBlock(cfg); // Check if loop has bounded iteration count - const isBounded = this.loopIsBounded(initialVar, conditionNode, updateResult); + const isBounded = this.loopIsBounded( + initialVar, + conditionNode, + updateResult + ); if (isBounded) { - this.buildBoundedLoop(cfg, branchBlock, mergeBlock, initialVar, phiNode, conditionNode, updateResult); + this.buildBoundedLoop( + cfg, + branchBlock, + mergeBlock, + initialVar, + phiNode, + conditionNode, + updateResult + ); } else { - this.buildUnboundedLoop(cfg, branchBlock, mergeBlock, initialVar, phiNode, conditionNode, updateResult); + this.buildUnboundedLoop( + cfg, + branchBlock, + mergeBlock, + initialVar, + phiNode, + conditionNode, + updateResult + ); } // Update the phi nodes created in buildBoundedLoop with actual body results @@ -50,7 +90,10 @@ export class StrandsFor { if (varName !== 'loopVar' && finalPhiNodes[varName]) { // Update the phi node's second input to use the actual body result const phiNodeID = finalPhiNodes[varName].id; - const phiNodeData = DAG.getNodeDataFromID(this.strandsContext.dag, phiNodeID); + const phiNodeData = DAG.getNodeDataFromID( + this.strandsContext.dag, + phiNodeID + ); // Update the dependsOn array to include the actual body result if (phiNodeData.dependsOn.length > 1) { phiNodeData.dependsOn[1] = resultNode.id; @@ -64,7 +107,9 @@ export class StrandsFor { // Create assignment nodes in the branch block for initial values CFG.pushBlockForModification(cfg, branchBlock); - for (const [varName, initialValueNode] of Object.entries(this.initialVars)) { + for (const [varName, initialValueNode] of Object.entries( + this.initialVars + )) { if (varName !== 'loopVar' && finalPhiNodes[varName]) { // Create an assignment statement: phiNode = initialValue const phiNodeID = finalPhiNodes[varName].id; @@ -75,7 +120,10 @@ export class StrandsFor { dependsOn: [phiNodeID, sourceNodeID], phiBlocks: [] }); - const assignmentID = DAG.getOrCreateNode(this.strandsContext.dag, assignmentNode); + const assignmentID = DAG.getOrCreateNode( + this.strandsContext.dag, + assignmentNode + ); CFG.recordInBasicBlock(cfg, branchBlock, assignmentID); } } @@ -97,7 +145,10 @@ export class StrandsFor { dependsOn: [phiNodeID, sourceNodeID], phiBlocks: [] }); - const assignmentID = DAG.getOrCreateNode(this.strandsContext.dag, assignmentNode); + const assignmentID = DAG.getOrCreateNode( + this.strandsContext.dag, + assignmentNode + ); CFG.recordInBasicBlock(cfg, this.finalBodyBlock, assignmentID); } } @@ -106,7 +157,11 @@ export class StrandsFor { // Convert phi nodes to StrandsNodes for the final result const finalBodyResults = {}; for (const [varName, phiNode] of Object.entries(finalPhiNodes)) { - finalBodyResults[varName] = createStrandsNode(phiNode.id, phiNode.dimension, this.strandsContext); + finalBodyResults[varName] = createStrandsNode( + phiNode.id, + phiNode.dimension, + this.strandsContext + ); } CFG.pushBlock(cfg, mergeBlock); @@ -114,7 +169,15 @@ export class StrandsFor { return finalBodyResults; } - buildBoundedLoop(cfg, branchBlock, mergeBlock, initialVar, phiNode, conditionNode, updateResult) { + buildBoundedLoop( + cfg, + branchBlock, + mergeBlock, + initialVar, + phiNode, + conditionNode, + updateResult + ) { // For bounded loops, create FOR block with three statements: init, condition, update const forBlock = CFG.createBasicBlock(cfg, BlockType.FOR); CFG.addEdge(cfg, branchBlock, forBlock); @@ -129,7 +192,10 @@ export class StrandsFor { dependsOn: [phiNode.id, initialVar.id], phiBlocks: [] }); - const initAssignmentID = DAG.getOrCreateNode(this.strandsContext.dag, initAssignmentNode); + const initAssignmentID = DAG.getOrCreateNode( + this.strandsContext.dag, + initAssignmentNode + ); CFG.recordInBasicBlock(cfg, forBlock, initAssignmentID); } @@ -140,7 +206,10 @@ export class StrandsFor { dependsOn: [conditionNode.id], phiBlocks: [] }); - const conditionStatementID = DAG.getOrCreateNode(this.strandsContext.dag, conditionStatementNode); + const conditionStatementID = DAG.getOrCreateNode( + this.strandsContext.dag, + conditionStatementNode + ); CFG.recordInBasicBlock(cfg, forBlock, conditionStatementID); // 3. Update statement - create assignment of update result to phi node @@ -149,7 +218,10 @@ export class StrandsFor { dependsOn: [phiNode.id, updateResult.id], phiBlocks: [] }); - const updateAssignmentID = DAG.getOrCreateNode(this.strandsContext.dag, updateAssignmentNode); + const updateAssignmentID = DAG.getOrCreateNode( + this.strandsContext.dag, + updateAssignmentNode + ); CFG.recordInBasicBlock(cfg, forBlock, updateAssignmentID); CFG.popBlock(cfg); @@ -158,7 +230,9 @@ export class StrandsFor { const instructions = cfg.blockInstructions[forBlock] || []; const expectedLength = initialVar ? 3 : 2; if (instructions.length !== expectedLength) { - throw new Error(`FOR block must have exactly ${expectedLength} statements, got ${instructions.length}`); + throw new Error( + `FOR block must have exactly ${expectedLength} statements, got ${instructions.length}` + ); } const scopeStartBlock = CFG.createBasicBlock(cfg, BlockType.SCOPE_START); @@ -175,7 +249,15 @@ export class StrandsFor { CFG.addEdge(cfg, scopeEndBlock, mergeBlock); } - buildUnboundedLoop(cfg, branchBlock, mergeBlock, initialVar, phiNode, conditionNode, updateResult) { + buildUnboundedLoop( + cfg, + branchBlock, + mergeBlock, + initialVar, + phiNode, + conditionNode, + updateResult + ) { // For unbounded loops, create FOR block with infinite loop and break condition const forBlock = CFG.createBasicBlock(cfg, BlockType.FOR); CFG.addEdge(cfg, branchBlock, forBlock); @@ -190,7 +272,10 @@ export class StrandsFor { dependsOn: [phiNode.id, initialVar.id], phiBlocks: [] }); - const initAssignmentID = DAG.getOrCreateNode(this.strandsContext.dag, initAssignmentNode); + const initAssignmentID = DAG.getOrCreateNode( + this.strandsContext.dag, + initAssignmentNode + ); CFG.recordInBasicBlock(cfg, forBlock, initAssignmentID); } else { // Create empty statement for init @@ -200,7 +285,10 @@ export class StrandsFor { dependsOn: [], phiBlocks: [] }); - const emptyInitID = DAG.getOrCreateNode(this.strandsContext.dag, emptyInitNode); + const emptyInitID = DAG.getOrCreateNode( + this.strandsContext.dag, + emptyInitNode + ); CFG.recordInBasicBlock(cfg, forBlock, emptyInitID); } @@ -211,7 +299,10 @@ export class StrandsFor { dependsOn: [], phiBlocks: [] }); - const emptyConditionID = DAG.getOrCreateNode(this.strandsContext.dag, emptyConditionNode); + const emptyConditionID = DAG.getOrCreateNode( + this.strandsContext.dag, + emptyConditionNode + ); CFG.recordInBasicBlock(cfg, forBlock, emptyConditionID); // 3. Update statement - empty for infinite loop @@ -221,7 +312,10 @@ export class StrandsFor { dependsOn: [], phiBlocks: [] }); - const emptyUpdateID = DAG.getOrCreateNode(this.strandsContext.dag, emptyUpdateNode); + const emptyUpdateID = DAG.getOrCreateNode( + this.strandsContext.dag, + emptyUpdateNode + ); CFG.recordInBasicBlock(cfg, forBlock, emptyUpdateID); CFG.popBlock(cfg); @@ -247,7 +341,10 @@ export class StrandsFor { cfg.blockConditions[breakConditionBlock] = negatedCondition.id; // Add scope start block for break statement - const breakScopeStartBlock = CFG.createBasicBlock(cfg, BlockType.SCOPE_START); + const breakScopeStartBlock = CFG.createBasicBlock( + cfg, + BlockType.SCOPE_START + ); CFG.addEdge(cfg, breakConditionBlock, breakScopeStartBlock); const breakStatementBlock = CFG.createBasicBlock(cfg, BlockType.DEFAULT); @@ -261,7 +358,10 @@ export class StrandsFor { dependsOn: [], phiBlocks: [] }); - const breakStatementID = DAG.getOrCreateNode(this.strandsContext.dag, breakStatementNode); + const breakStatementID = DAG.getOrCreateNode( + this.strandsContext.dag, + breakStatementNode + ); CFG.recordInBasicBlock(cfg, breakStatementBlock, breakStatementID); CFG.popBlock(cfg); @@ -290,7 +390,10 @@ export class StrandsFor { dependsOn: [phiNode.id, updateResult.id], phiBlocks: [] }); - const updateAssignmentID = DAG.getOrCreateNode(this.strandsContext.dag, updateAssignmentNode); + const updateAssignmentID = DAG.getOrCreateNode( + this.strandsContext.dag, + updateAssignmentNode + ); CFG.recordInBasicBlock(cfg, updateBlock, updateAssignmentID); CFG.popBlock(cfg); @@ -309,16 +412,24 @@ export class StrandsFor { let initialVar = this.initialCb(); // Convert to StrandsNode if it's not already one - if (!(initialVar?.isStrandsNode)) { - const { id, dimension } = primitiveConstructorNode(this.strandsContext, { baseType: BaseType.FLOAT, dimension: 1 }, initialVar); + if (!initialVar?.isStrandsNode) { + const { id, dimension } = primitiveConstructorNode( + this.strandsContext, + { baseType: BaseType.FLOAT, dimension: 1 }, + initialVar + ); initialVar = createStrandsNode(id, dimension, this.strandsContext); } // Create phi node for the loop variable in the BRANCH block - const phiNode = createPhiNode(this.strandsContext, [ - { value: initialVar, blockId: branchBlock }, - { value: initialVar, blockId: branchBlock } // Placeholder, will be updated later - ], 'loopVar'); + const phiNode = createPhiNode( + this.strandsContext, + [ + { value: initialVar, blockId: branchBlock }, + { value: initialVar, blockId: branchBlock } // Placeholder, will be updated later + ], + 'loopVar' + ); CFG.popBlock(cfg); return { initialVar, phiNode }; @@ -334,7 +445,10 @@ export class StrandsFor { phiBlocks: [], usedBy: [] }); - const notOperationID = DAG.getOrCreateNode(this.strandsContext.dag, notOperationNode); + const notOperationID = DAG.getOrCreateNode( + this.strandsContext.dag, + notOperationNode + ); return createStrandsNode(notOperationID, 1, this.strandsContext); } @@ -345,23 +459,39 @@ export class StrandsFor { const phiVars = {}; const phiNodesForBody = {}; CFG.pushBlockForModification(cfg, branchBlock); - for (const [varName, initialValueNode] of Object.entries(this.initialVars)) { + for (const [varName, initialValueNode] of Object.entries( + this.initialVars + )) { if (varName !== 'loopVar') { // Create phi node that will be used for the final result - const varPhiNode = createPhiNode(this.strandsContext, [ - { value: initialValueNode, blockId: branchBlock }, // Initial value - { value: initialValueNode, blockId: bodyBlock } // Placeholder - will update after body execution - ], varName); + const varPhiNode = createPhiNode( + this.strandsContext, + [ + { value: initialValueNode, blockId: branchBlock }, // Initial value + { value: initialValueNode, blockId: bodyBlock } // Placeholder - will update after body execution + ], + varName + ); phiNodesForBody[varName] = varPhiNode; - phiVars[varName] = createStrandsNode(varPhiNode.id, varPhiNode.dimension, this.strandsContext); + phiVars[varName] = createStrandsNode( + varPhiNode.id, + varPhiNode.dimension, + this.strandsContext + ); } } CFG.popBlock(cfg); - const loopVarNode = createStrandsNode(phiNode.id, phiNode.dimension, this.strandsContext); + const loopVarNode = createStrandsNode( + phiNode.id, + phiNode.dimension, + this.strandsContext + ); this.bodyResults = this.bodyCb(loopVarNode, phiVars) || {}; for (const key in this.bodyResults) { - this.bodyResults[key] = this.strandsContext.p5.strandsNode(this.bodyResults[key]); + this.bodyResults[key] = this.strandsContext.p5.strandsNode( + this.bodyResults[key] + ); } this.phiNodesForBody = phiNodesForBody; // Capture the final block after body execution before popping @@ -378,7 +508,10 @@ export class StrandsFor { if (!conditionNode) return false; // Analyze the condition node - it should be a comparison operation - const conditionData = DAG.getNodeDataFromID(this.strandsContext.dag, conditionNode.id); + const conditionData = DAG.getNodeDataFromID( + this.strandsContext.dag, + conditionNode.id + ); if (conditionData.nodeType !== NodeType.OPERATION) { return false; @@ -391,8 +524,16 @@ export class StrandsFor { } // Check if either operand uses only literals - const leftOperand = createStrandsNode(conditionData.dependsOn[0], 1, this.strandsContext); - const rightOperand = createStrandsNode(conditionData.dependsOn[1], 1, this.strandsContext); + const leftOperand = createStrandsNode( + conditionData.dependsOn[0], + 1, + this.strandsContext + ); + const rightOperand = createStrandsNode( + conditionData.dependsOn[1], + 1, + this.strandsContext + ); const leftUsesOnlyLiterals = this.nodeUsesOnlyLiterals(leftOperand); const rightUsesOnlyLiterals = this.nodeUsesOnlyLiterals(rightOperand); diff --git a/src/strands/strands_node.js b/src/strands/strands_node.js index 3973406659..bd45fb788f 100644 --- a/src/strands/strands_node.js +++ b/src/strands/strands_node.js @@ -1,4 +1,11 @@ -import { swizzleTrap, primitiveConstructorNode, variableNode, arrayAccessNode, arrayAssignmentNode, createStructArrayElementProxy } from './ir_builders'; +import { + swizzleTrap, + primitiveConstructorNode, + variableNode, + arrayAccessNode, + arrayAssignmentNode, + createStructArrayElementProxy +} from './ir_builders'; import { BaseType, NodeType, OpCode } from './ir_types'; import { getNodeDataFromID, createNodeData, getOrCreateNode } from './ir_dag'; import { recordInBasicBlock } from './ir_cfg'; @@ -59,8 +66,10 @@ export class StrandsNode { if (this._originalIdentifier) { const valueDim = value?.isStrandsNode ? value.dimension - : (Array.isArray(value) ? value.length : 1); - if (valueDim !== this._originalDimension && valueDim !== 1){ + : Array.isArray(value) + ? value.length + : 1; + if (valueDim !== this._originalDimension && valueDim !== 1) { dimensionMismatchError( this._originalDimension, valueDim, @@ -70,7 +79,10 @@ export class StrandsNode { // Create a variable node for the target (the varying variable) const { id: targetVarID } = variableNode( this.strandsContext, - { baseType: this._originalBaseType, dimension: this._originalDimension }, + { + baseType: this._originalBaseType, + dimension: this._originalDimension + }, this._originalIdentifier ); @@ -121,8 +133,10 @@ export class StrandsNode { if (this._originalIdentifier) { const valueDim = value?.isStrandsNode ? value.dimension - : (Array.isArray(value) ? value.length : 1); - if (valueDim !== swizzlePattern.length && valueDim !== 1){ + : Array.isArray(value) + ? value.length + : 1; + if (valueDim !== swizzlePattern.length && valueDim !== 1) { dimensionMismatchError( swizzlePattern.length, valueDim, @@ -132,7 +146,10 @@ export class StrandsNode { // Create a variable node for the target with swizzle const { id: targetVarID } = variableNode( this.strandsContext, - { baseType: this._originalBaseType, dimension: this._originalDimension }, + { + baseType: this._originalBaseType, + dimension: this._originalDimension + }, this._originalIdentifier ); @@ -177,7 +194,10 @@ export class StrandsNode { if (this._originalIdentifier) { const { id, dimension } = variableNode( this.strandsContext, - { baseType: this._originalBaseType, dimension: this._originalDimension }, + { + baseType: this._originalBaseType, + dimension: this._originalDimension + }, this._originalIdentifier ); return createStrandsNode(id, dimension, this.strandsContext); @@ -192,7 +212,12 @@ export class StrandsNode { // Validate baseType is 'storage' // For struct storage buffers, return a proxy with per-field getters/setters if (nodeData.baseType === 'storage' && this._schema) { - return createStructArrayElementProxy(this.strandsContext, this, index, this._schema); + return createStructArrayElementProxy( + this.strandsContext, + this, + index, + this._schema + ); } // Create array access node for storage and non-storage (vector) access @@ -212,14 +237,26 @@ export class StrandsNode { throw new Error('set() can only be used on storage buffers'); } if (!this._originalIdentifier) { - throw new Error('set() can only be used on storage buffers with an identifier'); + throw new Error( + 'set() can only be used on storage buffers with an identifier' + ); } // If value is a plain object (struct literal), expand to per-field assignments // e.g. buf[idx] = { position: pos, velocity: vel } // becomes buf[idx].position = pos; buf[idx].velocity = vel; - if (value !== null && typeof value === 'object' && !value.isStrandsNode && this._schema) { - const proxy = createStructArrayElementProxy(this.strandsContext, this, index, this._schema); + if ( + value !== null && + typeof value === 'object' && + !value.isStrandsNode && + this._schema + ) { + const proxy = createStructArrayElementProxy( + this.strandsContext, + this, + index, + this._schema + ); for (const [fieldName, fieldValue] of Object.entries(value)) { proxy[fieldName] = fieldValue; } diff --git a/src/strands/strands_phi_utils.js b/src/strands/strands_phi_utils.js index 86bcc7b59e..8dc90263c7 100644 --- a/src/strands/strands_phi_utils.js +++ b/src/strands/strands_phi_utils.js @@ -10,11 +10,15 @@ export function createPhiNode(strandsContext, phiInputs, varName) { } // Get dimension and baseType from first valid input, skipping ASSIGN_ON_USE nodes - const inputNodes = validInputs.map((input) => DAG.getNodeDataFromID(strandsContext.dag, input.value.id)); + const inputNodes = validInputs.map(input => + DAG.getNodeDataFromID(strandsContext.dag, input.value.id) + ); // Find first non-ASSIGN_ON_USE input to determine type - let typeSource = inputNodes.find((input) => input.baseType !== BaseType.ASSIGN_ON_USE && input.dimension) ?? - inputNodes.find((input) => input.baseType !== BaseType.ASSIGN_ON_USE); + let typeSource = + inputNodes.find( + input => input.baseType !== BaseType.ASSIGN_ON_USE && input.dimension + ) ?? inputNodes.find(input => input.baseType !== BaseType.ASSIGN_ON_USE); // If all are ASSIGN_ON_USE, fall back to first input if (!typeSource) { @@ -28,7 +32,12 @@ export function createPhiNode(strandsContext, phiInputs, varName) { if (baseType !== BaseType.ASSIGN_ON_USE) { for (const input of inputNodes) { if (input.baseType === BaseType.ASSIGN_ON_USE) { - DAG.propagateTypeToAssignOnUse(strandsContext.dag, input.id, baseType, dimension); + DAG.propagateTypeToAssignOnUse( + strandsContext.dag, + input.id, + baseType, + dimension + ); } } } @@ -42,7 +51,11 @@ export function createPhiNode(strandsContext, phiInputs, varName) { phiInputs // Store the full phi input information }; const id = DAG.getOrCreateNode(strandsContext.dag, nodeData); - CFG.recordInBasicBlock(strandsContext.cfg, strandsContext.cfg.currentBlock, id); + CFG.recordInBasicBlock( + strandsContext.cfg, + strandsContext.cfg.currentBlock, + id + ); return { id, dimension, diff --git a/src/strands/strands_ternary.js b/src/strands/strands_ternary.js index dcd84522ce..5344b06bfc 100644 --- a/src/strands/strands_ternary.js +++ b/src/strands/strands_ternary.js @@ -8,7 +8,9 @@ export function buildTernary(strandsContext, condition, ifTrue, ifFalse) { const { dag, cfg, p5 } = strandsContext; // Ensure all inputs are StrandsNodes - const condNode = condition?.isStrandsNode ? condition : p5.strandsNode(condition); + const condNode = condition?.isStrandsNode + ? condition + : p5.strandsNode(condition); const trueNode = ifTrue?.isStrandsNode ? ifTrue : p5.strandsNode(ifTrue); const falseNode = ifFalse?.isStrandsNode ? ifFalse : p5.strandsNode(ifFalse); @@ -17,11 +19,27 @@ export function buildTernary(strandsContext, condition, ifTrue, ifFalse) { let falseType = DAG.extractNodeTypeInfo(dag, falseNode.id); // Propagate type from the known branch to any ASSIGN_ON_USE branch - if (trueType.baseType === BaseType.ASSIGN_ON_USE && falseType.baseType !== BaseType.ASSIGN_ON_USE) { - DAG.propagateTypeToAssignOnUse(dag, trueNode.id, falseType.baseType, falseType.dimension); + if ( + trueType.baseType === BaseType.ASSIGN_ON_USE && + falseType.baseType !== BaseType.ASSIGN_ON_USE + ) { + DAG.propagateTypeToAssignOnUse( + dag, + trueNode.id, + falseType.baseType, + falseType.dimension + ); trueType = DAG.extractNodeTypeInfo(dag, trueNode.id); - } else if (falseType.baseType === BaseType.ASSIGN_ON_USE && trueType.baseType !== BaseType.ASSIGN_ON_USE) { - DAG.propagateTypeToAssignOnUse(dag, falseNode.id, trueType.baseType, trueType.dimension); + } else if ( + falseType.baseType === BaseType.ASSIGN_ON_USE && + trueType.baseType !== BaseType.ASSIGN_ON_USE + ) { + DAG.propagateTypeToAssignOnUse( + dag, + falseNode.id, + trueType.baseType, + trueType.dimension + ); falseType = DAG.extractNodeTypeInfo(dag, falseNode.id); } @@ -29,11 +47,13 @@ export function buildTernary(strandsContext, condition, ifTrue, ifFalse) { if ( trueType.baseType !== BaseType.ASSIGN_ON_USE && falseType.baseType !== BaseType.ASSIGN_ON_USE && - (trueType.baseType !== falseType.baseType || trueType.dimension !== falseType.dimension) + (trueType.baseType !== falseType.baseType || + trueType.dimension !== falseType.dimension) ) { - FES.userError('type error', + FES.userError( + 'type error', 'The true and false branches of a ternary expression must have the same type. ' + - `Right now, the true branch is a ${trueType.baseType}${trueType.dimension}, and the false branch is a ${falseType.baseType}${falseType.dimension}.` + `Right now, the true branch is a ${trueType.baseType}${trueType.dimension}, and the false branch is a ${falseType.baseType}${falseType.dimension}.` ); } @@ -44,7 +64,7 @@ export function buildTernary(strandsContext, condition, ifTrue, ifFalse) { opCode: OpCode.Nary.TERNARY, dependsOn: [condNode.id, trueNode.id, falseNode.id], baseType: resultType.baseType, - dimension: resultType.dimension, + dimension: resultType.dimension }); const id = DAG.getOrCreateNode(dag, nodeData); diff --git a/src/strands/strands_transpiler.js b/src/strands/strands_transpiler.js index 0d9310204a..f851a49663 100644 --- a/src/strands/strands_transpiler.js +++ b/src/strands/strands_transpiler.js @@ -7,46 +7,59 @@ let blockVarCounter = 0; let loopVarCounter = 0; function replaceBinaryOperator(codeSource) { switch (codeSource) { - case '+': return 'add'; - case '-': return 'sub'; - case '*': return 'mult'; - case '/': return 'div'; - case '%': return 'mod'; + case '+': + return 'add'; + case '-': + return 'sub'; + case '*': + return 'mult'; + case '/': + return 'div'; + case '%': + return 'mod'; case '==': - case '===': return 'equalTo'; + case '===': + return 'equalTo'; case '!=': - case '!==': return 'notEqual'; - case '>': return 'greaterThan'; - case '>=': return 'greaterEqual'; - case '<': return 'lessThan'; - case '<=': return 'lessEqual'; - case '&&': return 'and'; - case '||': return 'or'; + case '!==': + return 'notEqual'; + case '>': + return 'greaterThan'; + case '>=': + return 'greaterEqual'; + case '<': + return 'lessThan'; + case '<=': + return 'lessEqual'; + case '&&': + return 'and'; + case '||': + return 'or'; // TODO: handle ** --> pow, but make it stay pow in // GLSL instead of turning it back into ** } } function nodeIsUniform(ancestor) { - return ancestor && ancestor.type === 'CallExpression' - && ( - ( - // Global mode - ancestor.callee?.type === 'Identifier' && - ancestor.callee?.name.startsWith('uniform') - ) || ( - // Instance mode - ancestor.callee?.type === 'MemberExpression' && - ancestor.callee?.property.name.startsWith('uniform') - ) - ); + return ( + ancestor && + ancestor.type === 'CallExpression' && // Global mode + ((ancestor.callee?.type === 'Identifier' && + ancestor.callee?.name.startsWith('uniform')) || + // Instance mode + (ancestor.callee?.type === 'MemberExpression' && + ancestor.callee?.property.name.startsWith('uniform'))) + ); } function nodeIsUniformCallbackFn(node, names) { if (!names?.size) return false; - if (node.type === 'FunctionDeclaration' && names.has(node.id?.name)) return true; + if (node.type === 'FunctionDeclaration' && names.has(node.id?.name)) + return true; if ( - node.type === 'VariableDeclarator' && names.has(node.id?.name) && - (node.init?.type === 'FunctionExpression' || node.init?.type === 'ArrowFunctionExpression') + node.type === 'VariableDeclarator' && + names.has(node.id?.name) && + (node.init?.type === 'FunctionExpression' || + node.init?.type === 'ArrowFunctionExpression') ) { return true; } @@ -63,7 +76,8 @@ function collectUniformCallbackNames(ast) { VariableDeclarator(node) { if ( node.id?.type === 'Identifier' && - (node.init?.type === 'FunctionExpression' || node.init?.type === 'ArrowFunctionExpression') + (node.init?.type === 'FunctionExpression' || + node.init?.type === 'ArrowFunctionExpression') ) { namedFunctions.add(node.id.name); } @@ -86,18 +100,17 @@ function collectUniformCallbackNames(ast) { } function nodeIsVarying(node) { - return node && node.type === 'CallExpression' - && ( - ( - // Global mode - node.callee?.type === 'Identifier' && - (node.callee?.name.startsWith('varying') || node.callee?.name.startsWith('shared')) - ) || ( - // Instance mode - node.callee?.type === 'MemberExpression' && - (node.callee?.property.name.startsWith('varying') || node.callee?.property.name.startsWith('shared')) - ) - ); + return ( + node && + node.type === 'CallExpression' && // Global mode + ((node.callee?.type === 'Identifier' && + (node.callee?.name.startsWith('varying') || + node.callee?.name.startsWith('shared'))) || + // Instance mode + (node.callee?.type === 'MemberExpression' && + (node.callee?.property.name.startsWith('varying') || + node.callee?.property.name.startsWith('shared')))) + ); } // Convert static member expressions into dotted paths such as // `loopProtect.protect` so loop-protection calls can be matched reliably. @@ -110,9 +123,7 @@ function getMemberExpressionPath(node) { const objectPath = getMemberExpressionPath(node.object); const propertyName = node.property?.name; - return objectPath && propertyName - ? `${objectPath}.${propertyName}` - : null; + return objectPath && propertyName ? `${objectPath}.${propertyName}` : null; } // Detect calls added by loop protection before Strands tries to transpile them. @@ -161,24 +172,19 @@ function throwIfLoopProtectionInserted(ast) { function statementContainsStrandsControlFlow(stmt) { // Check for variable declarations with strands control flow init if (stmt.type === 'VariableDeclaration') { - const match = stmt.declarations.some(decl => - decl.init?.type === 'CallExpression' && - ( - ( - decl.init?.callee?.type === 'MemberExpression' && + const match = stmt.declarations.some( + decl => + decl.init?.type === 'CallExpression' && + ((decl.init?.callee?.type === 'MemberExpression' && decl.init?.callee?.object?.type === 'Identifier' && decl.init?.callee?.object?.name === '__p5' && (decl.init?.callee?.property?.name === 'strandsFor' || - decl.init?.callee?.property?.name === 'strandsIf') - ) || - ( - decl.init?.callee?.type === 'Identifier' && - (decl.init?.callee?.name === '__p5.strandsFor' || - decl.init?.callee?.name === '__p5.strandsIf') - ) - ) + decl.init?.callee?.property?.name === 'strandsIf')) || + (decl.init?.callee?.type === 'Identifier' && + (decl.init?.callee?.name === '__p5.strandsFor' || + decl.init?.callee?.name === '__p5.strandsIf'))) ); - return match + return match; } return false; } @@ -223,7 +229,7 @@ function buildPropertyPath(memberExpr) { // Replace all references to original variables with temp variables // and wrap literal assignments in strandsNode calls function replaceReferences(node, tempVarMap) { - const internalReplaceReferences = (node) => { + const internalReplaceReferences = node => { if (!node || typeof node !== 'object') return; // Check if this MemberExpression matches a tracked property path @@ -259,8 +265,11 @@ function replaceReferences(node, tempVarMap) { leftPath = buildPropertyPath(node.left); } - if (leftPath && tempVarMap.has(leftPath) && - (node.right.type === 'Literal' || node.right.type === 'ArrayExpression')) { + if ( + leftPath && + tempVarMap.has(leftPath) && + (node.right.type === 'Literal' || node.right.type === 'ArrayExpression') + ) { // Wrap the right hand side in a strandsNode call to make sure // it's not just a literal and has a type node.right = { @@ -278,7 +287,11 @@ function replaceReferences(node, tempVarMap) { for (const key in node) { if (node.hasOwnProperty(key) && key !== 'parent') { // Don't recurse into property names of non-computed member expressions - if (node.type === 'MemberExpression' && key === 'property' && !node.computed) { + if ( + node.type === 'MemberExpression' && + key === 'property' && + !node.computed + ) { continue; } if (Array.isArray(node[key])) { @@ -296,7 +309,7 @@ function replaceReferences(node, tempVarMap) { function replaceIdentifierReferences(node, oldName, newName) { if (!node || typeof node !== 'object') return node; - const replaceInNode = (n) => { + const replaceInNode = n => { if (!n || typeof n !== 'object') return n; if (n.type === 'Identifier' && n.name === oldName) { return { ...n, name: newName }; @@ -320,14 +333,20 @@ function replaceIdentifierReferences(node, oldName, newName) { // Shared handler for both BinaryExpression and LogicalExpression — // both follow the same operator-to-method-call transformation pattern. function transformBinaryOrLogical(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } node.left = { type: 'CallExpression', callee: { type: 'Identifier', - name: '__p5.strandsNode', + name: '__p5.strandsNode' }, arguments: [node.left] }; @@ -337,8 +356,8 @@ function transformBinaryOrLogical(node, state, ancestors) { object: node.left, property: { type: 'Identifier', - name: replaceBinaryOperator(node.operator), - }, + name: replaceBinaryOperator(node.operator) + } }; node.arguments = [node.right]; } @@ -377,20 +396,22 @@ function addCopyingAndReturn(functionBody, varsToReturn, sourcePrefix = null) { copyStatements.push({ type: 'VariableDeclaration', - declarations: [{ - type: 'VariableDeclarator', - id: { type: 'Identifier', name: tempName }, - init: { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: sourceExpr, - property: { type: 'Identifier', name: 'copy' }, - computed: false - }, - arguments: [] + declarations: [ + { + type: 'VariableDeclarator', + id: { type: 'Identifier', name: tempName }, + init: { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: sourceExpr, + property: { type: 'Identifier', name: 'copy' }, + computed: false + }, + arguments: [] + } } - }], + ], kind: 'let' }); } @@ -418,18 +439,24 @@ function addCopyingAndReturn(functionBody, varsToReturn, sourcePrefix = null) { const ASTCallbacks = { UnaryExpression(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } const unaryFnName = UnarySymbolToName[node.operator]; - const standardReplacement = (node) => { - node.type = 'CallExpression' + const standardReplacement = node => { + node.type = 'CallExpression'; node.callee = { type: 'Identifier', - name: `__p5.${unaryFnName}`, - } - node.arguments = [node.argument] - } + name: `__p5.${unaryFnName}` + }; + node.arguments = [node.argument]; + }; if (node.type === 'MemberExpression') { const property = node.argument.property.name; const swizzleSets = [ @@ -437,9 +464,12 @@ const ASTCallbacks = { ['r', 'g', 'b', 'a'], ['s', 't', 'p', 'q'] ]; - let isSwizzle = swizzleSets.some(set => - [...property].every(char => set.includes(char)) - ) && node.argument.type === 'MemberExpression' && !node.argument.computed; + let isSwizzle = + swizzleSets.some(set => + [...property].every(char => set.includes(char)) + ) && + node.argument.type === 'MemberExpression' && + !node.argument.computed; if (isSwizzle) { node.type = 'MemberExpression'; node.object = { @@ -448,7 +478,7 @@ const ASTCallbacks = { type: 'Identifier', name: `__p5.${unaryFnName}` }, - arguments: [node.argument.object], + arguments: [node.argument.object] }; node.property = { type: 'Identifier', @@ -464,7 +494,13 @@ const ASTCallbacks = { delete node.operator; }, BreakStatement(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } node.callee = { @@ -475,7 +511,13 @@ const ASTCallbacks = { node.type = 'CallExpression'; }, MemberExpression(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } // Skip sets -- these will be converted to .set() method @@ -496,7 +538,7 @@ const ASTCallbacks = { object: callee, property: { type: 'Identifier', - name: 'get', + name: 'get' } }; node.arguments = [member]; @@ -504,18 +546,26 @@ const ASTCallbacks = { } }, VariableDeclarator(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } if (nodeIsUniform(node.init)) { // Only inject the variable name if the first argument isn't already a string - if (node.init.arguments.length === 0 || - node.init.arguments[0].type !== 'Literal' || - typeof node.init.arguments[0].value !== 'string') { + if ( + node.init.arguments.length === 0 || + node.init.arguments[0].type !== 'Literal' || + typeof node.init.arguments[0].value !== 'string' + ) { const uniformNameLiteral = { type: 'Literal', value: node.id.name - } + }; node.init.arguments.unshift(uniformNameLiteral); } } @@ -529,7 +579,7 @@ const ASTCallbacks = { const varyingNameLiteral = { type: 'Literal', value: node.id.name - } + }; node.init.arguments.unshift(varyingNameLiteral); state.varyings[node.id.name] = varyingNameLiteral; } else { @@ -539,11 +589,18 @@ const ASTCallbacks = { } }, Identifier(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } - if (state.varyings[node.name] - && !ancestors.some(a => a.type === 'AssignmentExpression' && a.left === node) + if ( + state.varyings[node.name] && + !ancestors.some(a => a.type === 'AssignmentExpression' && a.left === node) ) { node.type = 'CallExpression'; node.callee = { @@ -555,7 +612,7 @@ const ASTCallbacks = { property: { type: 'Identifier', name: 'getValue' - }, + } }; node.arguments = []; } @@ -563,7 +620,13 @@ const ASTCallbacks = { // The callbacks for AssignmentExpression and BinaryExpression handle // operator overloading including +=, *= assignment expressions ArrayExpression(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } @@ -578,17 +641,23 @@ const ASTCallbacks = { node.type = 'CallExpression'; node.callee = { type: 'Identifier', - name: '__p5.strandsNode', + name: '__p5.strandsNode' }; node.arguments = [original]; }, AssignmentExpression(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier']; if (node.operator !== '=') { - const methodName = replaceBinaryOperator(node.operator.replace('=','')); + const methodName = replaceBinaryOperator(node.operator.replace('=', '')); const rightReplacementNode = { type: 'CallExpression', callee: { @@ -598,18 +667,18 @@ const ASTCallbacks = { type: 'CallExpression', callee: { type: 'Identifier', - name: '__p5.strandsNode', + name: '__p5.strandsNode' }, arguments: [node.left] } : node.left, property: { type: 'Identifier', - name: methodName, - }, + name: methodName + } }, arguments: [node.right] - } + }; node.operator = '='; node.right = rightReplacementNode; } @@ -626,11 +695,11 @@ const ASTCallbacks = { }, property: { type: 'Identifier', - name: 'bridge', + name: 'bridge' } }, - arguments: [node.right], - } + arguments: [node.right] + }; } // Handle swizzle assignment to varying variable: myVarying.xyz = value // Note: node.left.object might be worldPos.getValue() due to prior Identifier transformation @@ -659,15 +728,20 @@ const ASTCallbacks = { let varyingName = null; // Check if it's a direct identifier: myVarying.xyz - if (node.left.object.type === 'Identifier' && state.varyings[node.left.object.name]) { + if ( + node.left.object.type === 'Identifier' && + state.varyings[node.left.object.name] + ) { varyingName = node.left.object.name; } // Check if it's a getValue() call: myVarying.getValue().xyz - else if (node.left.object.type === 'CallExpression' && - node.left.object.callee?.type === 'MemberExpression' && - node.left.object.callee.property?.name === 'getValue' && - node.left.object.callee.object?.type === 'Identifier' && - state.varyings[node.left.object.callee.object.name]) { + else if ( + node.left.object.type === 'CallExpression' && + node.left.object.callee?.type === 'MemberExpression' && + node.left.object.callee.property?.name === 'getValue' && + node.left.object.callee.object?.type === 'Identifier' && + state.varyings[node.left.object.callee.object.name] + ) { varyingName = node.left.object.callee.object.name; } @@ -684,7 +758,7 @@ const ASTCallbacks = { }, property: { type: 'Identifier', - name: 'bridgeSwizzle', + name: 'bridgeSwizzle' } }, arguments: [ @@ -693,17 +767,22 @@ const ASTCallbacks = { value: swizzlePattern }, node.right - ], - } + ] + }; } } }, BinaryExpression: transformBinaryOrLogical, LogicalExpression: transformBinaryOrLogical, - ConditionalExpression(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } // Transform condition ? consequent : alternate @@ -719,7 +798,13 @@ const ASTCallbacks = { delete node.alternate; }, IfStatement(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } // Transform if statement into strandsIf() call @@ -729,10 +814,13 @@ const ASTCallbacks = { const thenFunction = { type: 'ArrowFunctionExpression', params: [], - body: node.consequent.type === 'BlockStatement' ? node.consequent : { - type: 'BlockStatement', - body: [node.consequent] - } + body: + node.consequent.type === 'BlockStatement' + ? node.consequent + : { + type: 'BlockStatement', + body: [node.consequent] + } }; // Start building the call chain: __p5.strandsIf(condition, then) let callExpression = { @@ -750,10 +838,13 @@ const ASTCallbacks = { elseFunction = { type: 'ArrowFunctionExpression', params: [], - body: node.alternate.type === 'BlockStatement' ? node.alternate : { - type: 'BlockStatement', - body: [node.alternate] - } + body: + node.alternate.type === 'BlockStatement' + ? node.alternate + : { + type: 'BlockStatement', + body: [node.alternate] + } }; } else { // Create an empty else function @@ -782,7 +873,7 @@ const ASTCallbacks = { // Analyze which outer scope variables are assigned in any branch const assignedVars = new Set(); - const analyzeBranch = (functionBody) => { + const analyzeBranch = functionBody => { // First pass: collect all variable declarations in the branch const localVars = new Set(); ancestor(functionBody, { @@ -866,18 +957,20 @@ const ASTCallbacks = { right: { type: 'CallExpression', callee: { type: 'Identifier', name: '__p5.strandsNode' }, - arguments: [rightArgExpr], + arguments: [rightArgExpr] } } }); } statements.push({ type: 'VariableDeclaration', - declarations: [{ - type: 'VariableDeclarator', - id: { type: 'Identifier', name: blockVar }, - init: callExpression - }], + declarations: [ + { + type: 'VariableDeclarator', + id: { type: 'Identifier', name: blockVar }, + init: callExpression + } + ], kind: 'const' }); // 2. Assignments for each modified variable @@ -926,7 +1019,13 @@ const ASTCallbacks = { delete node.alternate; }, UpdateExpression(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } @@ -963,7 +1062,13 @@ const ASTCallbacks = { ASTCallbacks.AssignmentExpression(node, state, ancestors); }, ForStatement(node, state, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) { + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) { return; } @@ -980,7 +1085,10 @@ const ASTCallbacks = { const declaration = node.init.declarations[0]; let initValue = declaration.init; - const initAst = { type: 'Program', body: [{ type: 'ExpressionStatement', expression: initValue }] }; + const initAst = { + type: 'Program', + body: [{ type: 'ExpressionStatement', expression: initValue }] + }; initValue = initAst.body[0].expression; initialFunction = { @@ -988,10 +1096,12 @@ const ASTCallbacks = { params: [], body: { type: 'BlockStatement', - body: [{ - type: 'ReturnStatement', - argument: initValue - }] + body: [ + { + type: 'ReturnStatement', + argument: initValue + } + ] } }; } else { @@ -1001,13 +1111,15 @@ const ASTCallbacks = { params: [], body: { type: 'BlockStatement', - body: [{ - type: 'ReturnStatement', - argument: { - type: 'Literal', - value: 0 + body: [ + { + type: 'ReturnStatement', + argument: { + type: 'Literal', + value: 0 + } } - }] + ] } }; } @@ -1017,9 +1129,16 @@ const ASTCallbacks = { // Replace loop variable references with the parameter if (node.init?.type === 'VariableDeclaration') { const loopVarName = node.init.declarations[0].id.name; - conditionBody = replaceIdentifierReferences(conditionBody, loopVarName, uniqueLoopVar); + conditionBody = replaceIdentifierReferences( + conditionBody, + loopVarName, + uniqueLoopVar + ); } - const conditionAst = { type: 'Program', body: [{ type: 'ExpressionStatement', expression: conditionBody }] }; + const conditionAst = { + type: 'Program', + body: [{ type: 'ExpressionStatement', expression: conditionBody }] + }; conditionBody = conditionAst.body[0].expression; const conditionFunction = { @@ -1035,9 +1154,16 @@ const ASTCallbacks = { // Replace loop variable references with the parameter if (node.init?.type === 'VariableDeclaration') { const loopVarName = node.init.declarations[0].id.name; - updateExpr = replaceIdentifierReferences(updateExpr, loopVarName, uniqueLoopVar); + updateExpr = replaceIdentifierReferences( + updateExpr, + loopVarName, + uniqueLoopVar + ); } - const updateAst = { type: 'Program', body: [{ type: 'ExpressionStatement', expression: updateExpr }] }; + const updateAst = { + type: 'Program', + body: [{ type: 'ExpressionStatement', expression: updateExpr }] + }; updateExpr = updateAst.body[0].expression; updateFunction = { @@ -1045,10 +1171,12 @@ const ASTCallbacks = { params: [{ type: 'Identifier', name: uniqueLoopVar }], body: { type: 'BlockStatement', - body: [{ - type: 'ReturnStatement', - argument: updateExpr - }] + body: [ + { + type: 'ReturnStatement', + argument: updateExpr + } + ] } }; } else { @@ -1057,24 +1185,33 @@ const ASTCallbacks = { params: [{ type: 'Identifier', name: uniqueLoopVar }], body: { type: 'BlockStatement', - body: [{ - type: 'ReturnStatement', - argument: { type: 'Identifier', name: uniqueLoopVar } - }] + body: [ + { + type: 'ReturnStatement', + argument: { type: 'Identifier', name: uniqueLoopVar } + } + ] } }; } // Create the body callback - let bodyBlock = node.body.type === 'BlockStatement' ? node.body : { - type: 'BlockStatement', - body: [node.body] - }; + let bodyBlock = + node.body.type === 'BlockStatement' + ? node.body + : { + type: 'BlockStatement', + body: [node.body] + }; // Replace loop variable references in the body if (node.init?.type === 'VariableDeclaration') { const loopVarName = node.init.declarations[0].id.name; - bodyBlock = replaceIdentifierReferences(bodyBlock, loopVarName, uniqueLoopVar); + bodyBlock = replaceIdentifierReferences( + bodyBlock, + loopVarName, + uniqueLoopVar + ); } const bodyFunction = { @@ -1106,7 +1243,7 @@ const ASTCallbacks = { AssignmentExpression(node, ancestors) { // Skip if we're inside a block that contains strands control flow if (ancestors.some(statementContainsStrandsControlFlow)) { - return + return; } const left = node.left; @@ -1129,7 +1266,6 @@ const ASTCallbacks = { }); if (assignedVars.size > 0) { - addCopyingAndReturn(bodyFunction.body, assignedVars, 'vars'); // Create block variable and assignments similar to if statements @@ -1172,16 +1308,24 @@ const ASTCallbacks = { type: 'Identifier', name: '__p5.strandsFor' }, - arguments: [initialFunction, conditionFunction, updateFunction, bodyFunction, initialVarsObject] + arguments: [ + initialFunction, + conditionFunction, + updateFunction, + bodyFunction, + initialVarsObject + ] }; statements.push({ type: 'VariableDeclaration', - declarations: [{ - type: 'VariableDeclarator', - id: { type: 'Identifier', name: blockVar }, - init: callExpression - }], + declarations: [ + { + type: 'VariableDeclarator', + id: { type: 'Identifier', name: blockVar }, + init: callExpression + } + ], kind: 'const' }); @@ -1233,21 +1377,24 @@ const ASTCallbacks = { type: 'Identifier', name: '__p5.strandsFor' }, - arguments: [initialFunction, conditionFunction, updateFunction, bodyFunction, { - type: 'ObjectExpression', - properties: [] - }] + arguments: [ + initialFunction, + conditionFunction, + updateFunction, + bodyFunction, + { + type: 'ObjectExpression', + properties: [] + } + ] }; } delete node.init; delete node.test; delete node.update; - }, - - - -} + } +}; // Helper function to check if a function body contains return statements in control flow function functionHasEarlyReturns(functionNode) { @@ -1309,11 +1456,13 @@ function transformHelperFunction(functionNode) { // 1. Add __returnValue declaration at the start of function body const returnValueDecl = { type: 'VariableDeclaration', - declarations: [{ - type: 'VariableDeclarator', - id: { type: 'Identifier', name: '__returnValue' }, - init: null - }], + declarations: [ + { + type: 'VariableDeclarator', + id: { type: 'Identifier', name: '__returnValue' }, + init: null + } + ], kind: 'let' }; @@ -1328,7 +1477,11 @@ function transformHelperFunction(functionNode) { for (let i = 0; i < statements.length; i++) { const stmt = statements[i]; - if (stmt.type === 'IfStatement' && blockContainsReturn(stmt.consequent) && !stmt.alternate) { + if ( + stmt.type === 'IfStatement' && + blockContainsReturn(stmt.consequent) && + !stmt.alternate + ) { // Find all subsequent statements const subsequentStatements = statements.slice(i + 1); @@ -1355,7 +1508,11 @@ function transformHelperFunction(functionNode) { if (stmt.alternate && stmt.alternate.type === 'BlockStatement') { restructureIfStatements(stmt.alternate.body); } - } else if (stmt.type === 'ForStatement' && stmt.body && stmt.body.type === 'BlockStatement') { + } else if ( + stmt.type === 'ForStatement' && + stmt.body && + stmt.body.type === 'BlockStatement' + ) { restructureIfStatements(stmt.body.body); } else if (stmt.type === 'BlockStatement') { restructureIfStatements(stmt.body); @@ -1414,9 +1571,11 @@ function functionHasSetInControlFlow(functionNode) { }, CallExpression(node) { // Check if this is a .set() call - if (inControlFlow > 0 && - node.callee?.type === 'MemberExpression' && - node.callee?.property?.name === 'set') { + if ( + inControlFlow > 0 && + node.callee?.type === 'MemberExpression' && + node.callee?.property?.name === 'set' + ) { hasSetInControlFlow = true; } } @@ -1441,9 +1600,11 @@ function transformFunctionSetCalls(functionNode) { // First pass: find all hooks that have .set() calls in control flow const findSetCalls = { CallExpression(node) { - if (node.callee?.type === 'MemberExpression' && - node.callee?.property?.name === 'set' && - node.callee?.object) { + if ( + node.callee?.type === 'MemberExpression' && + node.callee?.property?.name === 'set' && + node.callee?.object + ) { // This is something like filterColor.set(...) or myp5.filterColor.set(...) const hookObjectNode = node.callee.object; const exprString = escodegen.generate(hookObjectNode); @@ -1469,22 +1630,28 @@ function transformFunctionSetCalls(functionNode) { // 1. Find the .begin() call and insert intermediate variable right after it const intermediateVarDecl = { type: 'VariableDeclaration', - declarations: [{ - type: 'VariableDeclarator', - id: { type: 'Identifier', name: intermediateVarName }, - init: null - }], + declarations: [ + { + type: 'VariableDeclarator', + id: { type: 'Identifier', name: intermediateVarName }, + init: null + } + ], kind: 'let' }; let beginCallIndex = -1; for (let i = 0; i < functionNode.body.body.length; i++) { const stmt = functionNode.body.body[i]; - if (stmt.type === 'ExpressionStatement' && - stmt.expression?.type === 'CallExpression' && - stmt.expression?.callee?.type === 'MemberExpression' && - stmt.expression?.callee?.property?.name === 'begin') { - const beginExprString = escodegen.generate(stmt.expression.callee.object); + if ( + stmt.type === 'ExpressionStatement' && + stmt.expression?.type === 'CallExpression' && + stmt.expression?.callee?.type === 'MemberExpression' && + stmt.expression?.callee?.property?.name === 'begin' + ) { + const beginExprString = escodegen.generate( + stmt.expression.callee.object + ); if (beginExprString === exprString) { beginCallIndex = i; break; @@ -1503,9 +1670,11 @@ function transformFunctionSetCalls(functionNode) { const transformSetToAssignment = { CallExpression(node, state, ancestors) { // Check if this is a .set() call for this hook - if (node.callee?.type === 'MemberExpression' && - node.callee?.property?.name === 'set' && - node.callee?.object) { + if ( + node.callee?.type === 'MemberExpression' && + node.callee?.property?.name === 'set' && + node.callee?.object + ) { const currentExprString = escodegen.generate(node.callee.object); if (currentExprString === exprString && node.arguments.length > 0) { // Find the parent statement @@ -1553,10 +1722,12 @@ function transformFunctionSetCalls(functionNode) { let endCallIndex = -1; for (let i = 0; i < functionNode.body.body.length; i++) { const stmt = functionNode.body.body[i]; - if (stmt.type === 'ExpressionStatement' && - stmt.expression?.type === 'CallExpression' && - stmt.expression?.callee?.type === 'MemberExpression' && - stmt.expression?.callee?.property?.name === 'end') { + if ( + stmt.type === 'ExpressionStatement' && + stmt.expression?.type === 'CallExpression' && + stmt.expression?.callee?.type === 'MemberExpression' && + stmt.expression?.callee?.property?.name === 'end' + ) { const endExprString = escodegen.generate(stmt.expression.callee.object); if (endExprString === exprString) { endCallIndex = i; @@ -1570,9 +1741,14 @@ function transformFunctionSetCalls(functionNode) { functionNode.body.body.splice(endCallIndex, 0, finalSetCall); } else { // If no .end() found, insert before return statement or at the end - const lastStatement = functionNode.body.body[functionNode.body.body.length - 1]; + const lastStatement = + functionNode.body.body[functionNode.body.body.length - 1]; if (lastStatement && lastStatement.type === 'ReturnStatement') { - functionNode.body.body.splice(functionNode.body.body.length - 1, 0, finalSetCall); + functionNode.body.body.splice( + functionNode.body.body.length - 1, + 0, + finalSetCall + ); } else { functionNode.body.body.push(finalSetCall); } @@ -1587,7 +1763,11 @@ function transformSetCallsInControlFlow(ast, names) { // Collect functions that have .set() calls in control flow const collectFunctions = { ArrowFunctionExpression(node, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names))) { + if ( + ancestors.some( + a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names) + ) + ) { return; } if (functionHasSetInControlFlow(node)) { @@ -1595,7 +1775,11 @@ function transformSetCallsInControlFlow(ast, names) { } }, FunctionExpression(node, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names))) { + if ( + ancestors.some( + a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names) + ) + ) { return; } if (functionHasSetInControlFlow(node)) { @@ -1603,7 +1787,11 @@ function transformSetCallsInControlFlow(ast, names) { } }, FunctionDeclaration(node, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names))) { + if ( + ancestors.some( + a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names) + ) + ) { return; } if (functionHasSetInControlFlow(node)) { @@ -1627,18 +1815,30 @@ function transformHelperFunctionEarlyReturns(ast, names) { // Collect helper functions that need transformation const collectHelperFunctions = { VariableDeclarator(node, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names))) { + if ( + ancestors.some( + a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names) + ) + ) { return; } const init = node.init; - if (init && (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression')) { + if ( + init && + (init.type === 'ArrowFunctionExpression' || + init.type === 'FunctionExpression') + ) { if (functionHasEarlyReturns(init)) { helperFunctionsToTransform.push(init); } } }, FunctionDeclaration(node, ancestors) { - if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names))) { + if ( + ancestors.some( + a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names) + ) + ) { return; } if (functionHasEarlyReturns(node)) { @@ -1663,7 +1863,7 @@ function transformHelperFunctionEarlyReturns(ast, names) { /** * @private * @internal - * + * * Transpiles a p5.strands callback into executable JavaScript by applying * a multi-pass AST transformation pipeline. * @@ -1696,10 +1896,14 @@ function makeGuardedCallbacks(callbacks) { const guarded = {}; for (const [name, fn] of Object.entries(callbacks)) { guarded[name] = (node, state, ancestors) => { - if (ancestors.some(a => - nodeIsUniform(a) || - nodeIsUniformCallbackFn(a, state.uniformCallbackNames) - )) return; + if ( + ancestors.some( + a => + nodeIsUniform(a) || + nodeIsUniformCallbackFn(a, state.uniformCallbackNames) + ) + ) + return; return fn(node, state, ancestors); }; } @@ -1707,16 +1911,21 @@ function makeGuardedCallbacks(callbacks) { } function runNonControlFlowPass(ast, uniformCallbackNames) { - const nonControlFlowCallbacks = ({ ...ASTCallbacks }); + const nonControlFlowCallbacks = { ...ASTCallbacks }; delete nonControlFlowCallbacks.IfStatement; delete nonControlFlowCallbacks.ForStatement; - ancestor(ast, nonControlFlowCallbacks, undefined, { varyings: {}, uniformCallbackNames }); + ancestor(ast, nonControlFlowCallbacks, undefined, { + varyings: {}, + uniformCallbackNames + }); } function runControlFlowPass(ast, uniformCallbackNames) { const postOrderControlFlowTransform = { CallExpression(node, state, c) { - if (nodeIsUniform(node)) { return; } + if (nodeIsUniform(node)) { + return; + } if (node.callee) c(node.callee, state); for (const arg of node.arguments) c(arg, state); }, @@ -1727,8 +1936,11 @@ function runControlFlowPass(ast, uniformCallbackNames) { VariableDeclarator(node, state, c) { if ( state.uniformCallbackNames?.has(node.id?.name) && - (node.init?.type === 'FunctionExpression' || node.init?.type === 'ArrowFunctionExpression') - ) { return; } + (node.init?.type === 'FunctionExpression' || + node.init?.type === 'ArrowFunctionExpression') + ) { + return; + } if (node.init) c(node.init, state); }, IfStatement(node, state, c) { @@ -1759,14 +1971,20 @@ function runControlFlowPass(ast, uniformCallbackNames) { delete node.argument; } }; - recursive(ast, { varyings: {}, inControlFlow: 0, uniformCallbackNames }, postOrderControlFlowTransform); + recursive( + ast, + { varyings: {}, inControlFlow: 0, uniformCallbackNames }, + postOrderControlFlowTransform + ); } function buildStrandsCallback(p5, ast, scope) { const transpiledSource = escodegen.generate(ast); const scopeKeys = Object.keys(scope); - const match = /\(?\s*(?:function)?\s*\w*\s*\(([^)]*)\)\s*(?:=>)?\s*{((?:.|\n)*)}\s*;?\s*\)?/ - .exec(transpiledSource); + const match = + /\(?\s*(?:function)?\s*\w*\s*\(([^)]*)\)\s*(?:=>)?\s*{((?:.|\n)*)}\s*;?\s*\)?/.exec( + transpiledSource + ); if (!match) { console.log(transpiledSource); throw new Error('Could not parse p5.strands function!'); @@ -1796,13 +2014,14 @@ function buildStrandsCallback(p5, ast, scope) { } } - - export function transpileStrandsToJS(p5, sourceString, srcLocations, scope) { blockVarCounter = 0; loopVarCounter = 0; - const ast = parse(sourceString, { ecmaVersion: 2021, locations: srcLocations }); + const ast = parse(sourceString, { + ecmaVersion: 2021, + locations: srcLocations + }); throwIfLoopProtectionInserted(ast); diff --git a/src/type/index.js b/src/type/index.js index 6e74cd27f1..61a54b748b 100644 --- a/src/type/index.js +++ b/src/type/index.js @@ -1,9 +1,7 @@ - import textCore from './textCore.js'; import p5font from './p5.Font.js'; -export default function(p5){ +export default function (p5) { p5.registerAddon(textCore); p5.registerAddon(p5font); } - diff --git a/src/type/lib/Typr.js b/src/type/lib/Typr.js index ec7e94a5ec..6d389d330f 100644 --- a/src/type/lib/Typr.js +++ b/src/type/lib/Typr.js @@ -1,68 +1,70 @@ -import { inflate } from 'pako'; +import { inflate } from 'pako/lib/inflate'; // Mocking the pako module to just have inflate for a smaller package size const pako = { inflate }; var Typr = {}; -Typr["parse"] = function (buff) { - var bin = Typr["B"]; +Typr['parse'] = function (buff) { + var bin = Typr['B']; var readFont = function (data, idx, offset, tmap) { - var T = Typr["T"]; + var T = Typr['T']; var prsr = { - "cmap": T.cmap, - "head": T.head, - "hhea": T.hhea, - "maxp": T.maxp, - "hmtx": T.hmtx, - "name": T.name, - "OS/2": T.OS2, - "post": T.post, - - "loca": T.loca, - "kern": T.kern, - "glyf": T.glyf, - - "CFF ": T.CFF, + cmap: T.cmap, + head: T.head, + hhea: T.hhea, + maxp: T.maxp, + hmtx: T.hmtx, + name: T.name, + 'OS/2': T.OS2, + post: T.post, + + loca: T.loca, + kern: T.kern, + glyf: T.glyf, + + 'CFF ': T.CFF, /* "GPOS", "GSUB", "GDEF",*/ - "GSUB": T.GSUB, - "CBLC": T.CBLC, - "CBDT": T.CBDT, - - "SVG ": T.SVG, - "COLR": T.colr, - "CPAL": T.cpal, - "sbix": T.sbix, - - "fvar": T.fvar, - "gvar": T.gvar, - "avar": T.avar, - "HVAR": T.HVAR + GSUB: T.GSUB, + CBLC: T.CBLC, + CBDT: T.CBDT, + + 'SVG ': T.SVG, + COLR: T.colr, + CPAL: T.cpal, + sbix: T.sbix, + + fvar: T.fvar, + gvar: T.gvar, + avar: T.avar, + HVAR: T.HVAR //"VORG", }; - var obj = { "_data": data, "_index": idx, "_offset": offset }; + var obj = { _data: data, _index: idx, _offset: offset }; for (var t in prsr) { - var tab = Typr["findTable"](data, t, offset); + var tab = Typr['findTable'](data, t, offset); if (tab) { - var off = tab[0], tobj = tmap[off]; + var off = tab[0], + tobj = tmap[off]; if (tobj == null) tobj = prsr[t].parseTab(data, off, tab[1], obj); obj[t] = tmap[off] = tobj; } } return obj; - } + }; function woffToOtf(data) { var numTables = bin.readUshort(data, 12); var totalSize = bin.readUint(data, 16); - var otf = new Uint8Array(totalSize), toff = 12 + numTables * 16; + var otf = new Uint8Array(totalSize), + toff = 12 + numTables * 16; - bin.writeASCII(otf, 0, "OTTO"); + bin.writeASCII(otf, 0, 'OTTO'); bin.writeUshort(otf, 4, numTables); var off = 44; @@ -75,20 +77,20 @@ Typr["parse"] = function (buff) { //console.log(i, ":::", tag,tof,oLe); var tab = data.slice(tof, tof + cLe); - if (cLe != oLe) tab = pako["inflate"](tab); + if (cLe != oLe) tab = pako['inflate'](tab); var to = 12 + i * 16; bin.writeASCII(otf, to, tag); bin.writeUint(otf, to + 8, toff); bin.writeUint(otf, to + 12, oLe); - otf.set(tab, toff); toff += oLe; + otf.set(tab, toff); + toff += oLe; } //console.log(otf); return otf; } - var data = new Uint8Array(buff); // PATCHED: keep around the compressed data if we inflate it let compressedData; @@ -99,45 +101,53 @@ Typr["parse"] = function (buff) { var tmap = {}; var tag = bin.readASCII(data, 0, 4); - if (tag == "ttcf") { + if (tag == 'ttcf') { var offset = 4; - var majV = bin.readUshort(data, offset); offset += 2; - var minV = bin.readUshort(data, offset); offset += 2; - var numF = bin.readUint(data, offset); offset += 4; + var majV = bin.readUshort(data, offset); + offset += 2; + var minV = bin.readUshort(data, offset); + offset += 2; + var numF = bin.readUint(data, offset); + offset += 4; var fnts = []; for (var i = 0; i < numF; i++) { - var foff = bin.readUint(data, offset); offset += 4; + var foff = bin.readUint(data, offset); + offset += 4; fnts.push(readFont(data, i, foff, tmap)); } return fnts; } - var fnt = readFont(data, 0, 0, tmap); //console.log(fnt); throw "e"; + var fnt = readFont(data, 0, 0, tmap); //console.log(fnt); throw "e"; fnt._compressedData = compressedData; // PATCH: make compressed data accessible - var fvar = fnt["fvar"]; + var fvar = fnt['fvar']; if (fvar) { var out = [fnt]; for (var i = 0; i < fvar[1].length; i++) { var fv = fvar[1][i]; - var obj = {}; out.push(obj); for (var p in fnt) obj[p] = fnt[p]; - obj["_index"] = i; - var name = obj["name"] = JSON.parse(JSON.stringify(obj["name"])); - name["fontSubfamily"] = fv[0]; - if (fv[3] == null) fv[3] = (name["fontFamily"] + "-" + name["fontSubfamily"])["replaceAll"](" ", ""); - name["postScriptName"] = fv[3]; + var obj = {}; + out.push(obj); + for (var p in fnt) obj[p] = fnt[p]; + obj['_index'] = i; + var name = (obj['name'] = JSON.parse(JSON.stringify(obj['name']))); + name['fontSubfamily'] = fv[0]; + if (fv[3] == null) + fv[3] = (name['fontFamily'] + '-' + name['fontSubfamily'])[ + 'replaceAll' + ](' ', ''); + name['postScriptName'] = fv[3]; } return out; } return [fnt]; -} - +}; -Typr["findTable"] = function (data, tab, foff) { - var bin = Typr["B"]; +Typr['findTable'] = function (data, tab, foff) { + var bin = Typr['B']; var numTables = bin.readUshort(data, foff + 4); var offset = foff + 12; for (var i = 0; i < numTables; i++) { - var tag = bin.readASCII(data, offset, 4); //console.log(tag); + var tag = bin.readASCII(data, offset, 4); //console.log(tag); var checkSum = bin.readUint(data, offset + 4); var toffset = bin.readUint(data, offset + 8); var length = bin.readUint(data, offset + 12); @@ -145,7 +155,7 @@ Typr["findTable"] = function (data, tab, foff) { offset += 16; } return null; -} +}; /* Typr["splitBy"] = function(data,tag) { data = new Uint8Array(data); console.log(data.slice(0,64)); @@ -238,96 +248,104 @@ Typr["_joinArrs"] = function(tabs) { } */ -Typr["T"] = {}; +Typr['T'] = {}; - - - - -Typr["B"] = { +Typr['B'] = { readFixed: function (data, o) { - return ((data[o] << 8) | data[o + 1]) + (((data[o + 2] << 8) | data[o + 3]) / (256 * 256 + 4)); + return ( + ((data[o] << 8) | data[o + 1]) + + ((data[o + 2] << 8) | data[o + 3]) / (256 * 256 + 4) + ); }, readF2dot14: function (data, o) { - var num = Typr["B"].readShort(data, o); + var num = Typr['B'].readShort(data, o); return num / 16384; }, readInt: function (buff, p) { //if(p>=buff.length) throw "error"; - var a = Typr["B"].t.uint8; + var a = Typr['B'].t.uint8; a[0] = buff[p + 3]; a[1] = buff[p + 2]; a[2] = buff[p + 1]; a[3] = buff[p]; - return Typr["B"].t.int32[0]; + return Typr['B'].t.int32[0]; }, readInt8: function (buff, p) { //if(p>=buff.length) throw "error"; - var a = Typr["B"].t.uint8; + var a = Typr['B'].t.uint8; a[0] = buff[p]; - return Typr["B"].t.int8[0]; + return Typr['B'].t.int8[0]; }, readShort: function (buff, p) { //if(p>=buff.length) throw "error"; - var a = Typr["B"].t.uint16; + var a = Typr['B'].t.uint16; a[0] = (buff[p] << 8) | buff[p + 1]; - return Typr["B"].t.int16[0]; + return Typr['B'].t.int16[0]; }, readUshort: function (buff, p) { //if(p>=buff.length) throw "error"; return (buff[p] << 8) | buff[p + 1]; }, writeUshort: function (buff, p, n) { - buff[p] = (n >> 8) & 255; buff[p + 1] = n & 255; + buff[p] = (n >> 8) & 255; + buff[p + 1] = n & 255; }, readUshorts: function (buff, p, len) { var arr = []; for (var i = 0; i < len; i++) { - var v = Typr["B"].readUshort(buff, p + i * 2); //if(v==932) console.log(p+i*2); + var v = Typr['B'].readUshort(buff, p + i * 2); //if(v==932) console.log(p+i*2); arr.push(v); } return arr; }, readUint: function (buff, p) { //if(p>=buff.length) throw "error"; - var a = Typr["B"].t.uint8; - a[3] = buff[p]; a[2] = buff[p + 1]; a[1] = buff[p + 2]; a[0] = buff[p + 3]; - return Typr["B"].t.uint32[0]; + var a = Typr['B'].t.uint8; + a[3] = buff[p]; + a[2] = buff[p + 1]; + a[1] = buff[p + 2]; + a[0] = buff[p + 3]; + return Typr['B'].t.uint32[0]; }, writeUint: function (buff, p, n) { - buff[p] = (n >> 24) & 255; buff[p + 1] = (n >> 16) & 255; buff[p + 2] = (n >> 8) & 255; buff[p + 3] = (n >> 0) & 255; + buff[p] = (n >> 24) & 255; + buff[p + 1] = (n >> 16) & 255; + buff[p + 2] = (n >> 8) & 255; + buff[p + 3] = (n >> 0) & 255; }, readUint64: function (buff, p) { //if(p>=buff.length) throw "error"; - return (Typr["B"].readUint(buff, p) * (0xffffffff + 1)) + Typr["B"].readUint(buff, p + 4); + return ( + Typr['B'].readUint(buff, p) * (0xffffffff + 1) + + Typr['B'].readUint(buff, p + 4) + ); }, - readASCII: function (buff, p, l) // l : length in Characters (not Bytes) - { + readASCII: function (buff, p, l) { + // l : length in Characters (not Bytes) //if(p>=buff.length) throw "error"; - var s = ""; + var s = ''; for (var i = 0; i < l; i++) s += String.fromCharCode(buff[p + i]); return s; }, - writeASCII: function (buff, p, s) // l : length in Characters (not Bytes) - { - for (var i = 0; i < s.length; i++) - buff[p + i] = s.charCodeAt(i); + writeASCII: function (buff, p, s) { + // l : length in Characters (not Bytes) + for (var i = 0; i < s.length; i++) buff[p + i] = s.charCodeAt(i); }, readUnicode: function (buff, p, l) { //if(p>=buff.length) throw "error"; - var s = ""; + var s = ''; for (var i = 0; i < l; i++) { var c = (buff[p++] << 8) | buff[p++]; s += String.fromCharCode(c); } return s; }, - _tdec: globalThis["TextDecoder"] ? new globalThis["TextDecoder"]() : null, + _tdec: globalThis['TextDecoder'] ? new globalThis['TextDecoder']() : null, readUTF8: function (buff, p, l) { - var tdec = Typr["B"]._tdec; - if (tdec && p == 0 && l == buff.length) return tdec["decode"](buff); - return Typr["B"].readASCII(buff, p, l); + var tdec = Typr['B']._tdec; + if (tdec && p == 0 && l == buff.length) return tdec['decode'](buff); + return Typr['B'].readASCII(buff, p, l); }, readBytes: function (buff, p, l) { //if(p>=buff.length) throw "error"; @@ -335,15 +353,14 @@ Typr["B"] = { for (var i = 0; i < l; i++) arr.push(buff[p + i]); return arr; }, - readASCIIArray: function (buff, p, l) // l : length in Characters (not Bytes) - { + readASCIIArray: function (buff, p, l) { + // l : length in Characters (not Bytes) //if(p>=buff.length) throw "error"; var s = []; - for (var i = 0; i < l; i++) - s.push(String.fromCharCode(buff[p + i])); + for (var i = 0; i < l; i++) s.push(String.fromCharCode(buff[p + i])); return s; }, - t: function () { + t: (function () { var ab = new ArrayBuffer(8); return { buff: ab, @@ -353,28 +370,27 @@ Typr["B"] = { uint16: new Uint16Array(ab), int32: new Int32Array(ab), uint32: new Uint32Array(ab) - } - }() + }; + })() }; - - - - - -Typr["T"].CFF = { +Typr['T'].CFF = { parseTab: function (data, offset, length) { - var bin = Typr["B"]; - var CFF = Typr["T"].CFF; + var bin = Typr['B']; + var CFF = Typr['T'].CFF; data = new Uint8Array(data.buffer, offset, length); offset = 0; // Header - var major = data[offset]; offset++; - var minor = data[offset]; offset++; - var hdrSize = data[offset]; offset++; - var offsize = data[offset]; offset++; + var major = data[offset]; + offset++; + var minor = data[offset]; + offset++; + var hdrSize = data[offset]; + offset++; + var offsize = data[offset]; + offset++; //console.log(major, minor, hdrSize, offsize); // Name INDEX @@ -382,16 +398,21 @@ Typr["T"].CFF = { offset = CFF.readIndex(data, offset, ninds); var names = []; - for (var i = 0; i < ninds.length - 1; i++) names.push(bin.readASCII(data, offset + ninds[i], ninds[i + 1] - ninds[i])); + for (var i = 0; i < ninds.length - 1; i++) + names.push( + bin.readASCII(data, offset + ninds[i], ninds[i + 1] - ninds[i]) + ); offset += ninds[ninds.length - 1]; - // Top DICT INDEX var tdinds = []; - offset = CFF.readIndex(data, offset, tdinds); //console.log(tdinds); + offset = CFF.readIndex(data, offset, tdinds); //console.log(tdinds); // Top DICT Data var topDicts = []; - for (var i = 0; i < tdinds.length - 1; i++) topDicts.push(CFF.readDict(data, offset + tdinds[i], offset + tdinds[i + 1])); + for (var i = 0; i < tdinds.length - 1; i++) + topDicts.push( + CFF.readDict(data, offset + tdinds[i], offset + tdinds[i + 1]) + ); offset += tdinds[tdinds.length - 1]; var topdict = topDicts[0]; //console.log(topdict); @@ -401,7 +422,10 @@ Typr["T"].CFF = { offset = CFF.readIndex(data, offset, sinds); // String Data var strings = []; - for (var i = 0; i < sinds.length - 1; i++) strings.push(bin.readASCII(data, offset + sinds[i], sinds[i + 1] - sinds[i])); + for (var i = 0; i < sinds.length - 1; i++) + strings.push( + bin.readASCII(data, offset + sinds[i], sinds[i + 1] - sinds[i]) + ); offset += sinds[sinds.length - 1]; // Global Subr INDEX (subroutines) @@ -409,71 +433,97 @@ Typr["T"].CFF = { // charstrings - if (topdict["CharStrings"]) topdict["CharStrings"] = CFF.readBytes(data, topdict["CharStrings"]); + if (topdict['CharStrings']) + topdict['CharStrings'] = CFF.readBytes(data, topdict['CharStrings']); // CID font - if (topdict["ROS"]) { - offset = topdict["FDArray"]; + if (topdict['ROS']) { + offset = topdict['FDArray']; var fdind = []; offset = CFF.readIndex(data, offset, fdind); - topdict["FDArray"] = []; + topdict['FDArray'] = []; for (var i = 0; i < fdind.length - 1; i++) { var dict = CFF.readDict(data, offset + fdind[i], offset + fdind[i + 1]); CFF._readFDict(data, dict, strings); - topdict["FDArray"].push(dict); + topdict['FDArray'].push(dict); } offset += fdind[fdind.length - 1]; - offset = topdict["FDSelect"]; - topdict["FDSelect"] = []; - var fmt = data[offset]; offset++; + offset = topdict['FDSelect']; + topdict['FDSelect'] = []; + var fmt = data[offset]; + offset++; if (fmt == 3) { - var rns = bin.readUshort(data, offset); offset += 2; + var rns = bin.readUshort(data, offset); + offset += 2; for (var i = 0; i < rns + 1; i++) { - topdict["FDSelect"].push(bin.readUshort(data, offset), data[offset + 2]); offset += 3; + topdict['FDSelect'].push( + bin.readUshort(data, offset), + data[offset + 2] + ); + offset += 3; } - } - else throw fmt; + } else throw fmt; } // Encoding //if(topdict["Encoding"]) topdict["Encoding"] = CFF.readEncoding(data, topdict["Encoding"], topdict["CharStrings"].length); // charset - if (topdict["charset"]) topdict["charset"] = CFF.readCharset(data, topdict["charset"], topdict["CharStrings"].length); + if (topdict['charset']) + topdict['charset'] = CFF.readCharset( + data, + topdict['charset'], + topdict['CharStrings'].length + ); CFF._readFDict(data, topdict, strings); return topdict; }, _readFDict: function (data, dict, ss) { - var CFF = Typr["T"].CFF; + var CFF = Typr['T'].CFF; var offset; - if (dict["Private"]) { - offset = dict["Private"][1]; - dict["Private"] = CFF.readDict(data, offset, offset + dict["Private"][0]); - if (dict["Private"]["Subrs"]) CFF.readSubrs(data, offset + dict["Private"]["Subrs"], dict["Private"]); - } - for (var p in dict) if (["FamilyName", "FontName", "FullName", "Notice", "version", "Copyright"].indexOf(p) != -1) dict[p] = ss[dict[p] - 426 + 35]; + if (dict['Private']) { + offset = dict['Private'][1]; + dict['Private'] = CFF.readDict(data, offset, offset + dict['Private'][0]); + if (dict['Private']['Subrs']) + CFF.readSubrs(data, offset + dict['Private']['Subrs'], dict['Private']); + } + for (var p in dict) + if ( + [ + 'FamilyName', + 'FontName', + 'FullName', + 'Notice', + 'version', + 'Copyright' + ].indexOf(p) != -1 + ) + dict[p] = ss[dict[p] - 426 + 35]; }, readSubrs: function (data, offset, obj) { - obj["Subrs"] = Typr["T"].CFF.readBytes(data, offset); + obj['Subrs'] = Typr['T'].CFF.readBytes(data, offset); - var bias, nSubrs = obj["Subrs"].length + 1; + var bias, + nSubrs = obj['Subrs'].length + 1; if (false) bias = 0; else if (nSubrs < 1240) bias = 107; else if (nSubrs < 33900) bias = 1131; else bias = 32768; - obj["Bias"] = bias; + obj['Bias'] = bias; }, readBytes: function (data, offset) { - var bin = Typr["B"]; + var bin = Typr['B']; var arr = []; - offset = Typr["T"].CFF.readIndex(data, offset, arr); + offset = Typr['T'].CFF.readIndex(data, offset, arr); - var subrs = [], arl = arr.length - 1, no = data.byteOffset + offset; + var subrs = [], + arl = arr.length - 1, + no = data.byteOffset + offset; for (var i = 0; i < arl; i++) { var ari = arr[i]; subrs.push(new Uint8Array(data.buffer, no + ari, arr[i + 1] - ari)); @@ -482,49 +532,31 @@ Typr["T"].CFF = { }, tableSE: [ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71, 72, - 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87, 88, - 89, 90, 91, 92, 93, 94, 95, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 96, 97, 98, 99, 100, 101, 102, - 103, 104, 105, 106, 107, 108, 109, 110, - 0, 111, 112, 113, 114, 0, 115, 116, - 117, 118, 119, 120, 121, 122, 0, 123, - 0, 124, 125, 126, 127, 128, 129, 130, - 131, 0, 132, 133, 0, 134, 135, 136, - 137, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 138, 0, 139, 0, 0, 0, 0, - 140, 141, 142, 143, 0, 0, 0, 0, - 0, 144, 0, 0, 0, 145, 0, 0, - 146, 147, 148, 149, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 0, 111, 112, 113, 114, 0, 115, 116, 117, + 118, 119, 120, 121, 122, 0, 123, 0, 124, 125, 126, 127, 128, 129, 130, 131, + 0, 132, 133, 0, 134, 135, 136, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 138, 0, 139, 0, 0, 0, 0, 140, 141, 142, 143, 0, 0, 0, 0, 0, 144, 0, + 0, 0, 145, 0, 0, 146, 147, 148, 149, 0, 0, 0, 0 ], glyphByUnicode: function (cff, code) { - for (var i = 0; i < cff["charset"].length; i++) if (cff["charset"][i] == code) return i; + for (var i = 0; i < cff['charset'].length; i++) + if (cff['charset'][i] == code) return i; return -1; }, - glyphBySE: function (cff, charcode) // glyph by standard encoding - { + glyphBySE: function (cff, charcode) { + // glyph by standard encoding if (charcode < 0 || charcode > 255) return -1; - return Typr["T"].CFF.glyphByUnicode(cff, Typr["T"].CFF.tableSE[charcode]); + return Typr['T'].CFF.glyphByUnicode(cff, Typr['T'].CFF.tableSE[charcode]); }, /*readEncoding : function(data, offset, num) @@ -560,91 +592,177 @@ Typr["T"].CFF = { },*/ readCharset: function (data, offset, num) { - var bin = Typr["B"]; + var bin = Typr['B']; var charset = ['.notdef']; - var format = data[offset]; offset++; + var format = data[offset]; + offset++; if (format == 0) { for (var i = 0; i < num; i++) { - var first = bin.readUshort(data, offset); offset += 2; + var first = bin.readUshort(data, offset); + offset += 2; charset.push(first); } - } - else if (format == 1 || format == 2) { + } else if (format == 1 || format == 2) { while (charset.length < num) { - var first = bin.readUshort(data, offset); offset += 2; + var first = bin.readUshort(data, offset); + offset += 2; var nLeft = 0; - if (format == 1) { nLeft = data[offset]; offset++; } - else { nLeft = bin.readUshort(data, offset); offset += 2; } - for (var i = 0; i <= nLeft; i++) { charset.push(first); first++; } + if (format == 1) { + nLeft = data[offset]; + offset++; + } else { + nLeft = bin.readUshort(data, offset); + offset += 2; + } + for (var i = 0; i <= nLeft; i++) { + charset.push(first); + first++; + } } - } - else throw "error: format: " + format; + } else throw 'error: format: ' + format; return charset; }, readIndex: function (data, offset, inds) { - var bin = Typr["B"]; - - var count = bin.readUshort(data, offset) + 1; offset += 2; - var offsize = data[offset]; offset++; + var bin = Typr['B']; - if (offsize == 1) for (var i = 0; i < count; i++) inds.push(data[offset + i]); - else if (offsize == 2) for (var i = 0; i < count; i++) inds.push(bin.readUshort(data, offset + i * 2)); - else if (offsize == 3) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 3 - 1) & 0x00ffffff); - else if (offsize == 4) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 4)); - else if (count != 1) throw "unsupported offset size: " + offsize + ", count: " + count; + var count = bin.readUshort(data, offset) + 1; + offset += 2; + var offsize = data[offset]; + offset++; + + if (offsize == 1) + for (var i = 0; i < count; i++) inds.push(data[offset + i]); + else if (offsize == 2) + for (var i = 0; i < count; i++) + inds.push(bin.readUshort(data, offset + i * 2)); + else if (offsize == 3) + for (var i = 0; i < count; i++) + inds.push(bin.readUint(data, offset + i * 3 - 1) & 0x00ffffff); + else if (offsize == 4) + for (var i = 0; i < count; i++) + inds.push(bin.readUint(data, offset + i * 4)); + else if (count != 1) + throw 'unsupported offset size: ' + offsize + ', count: ' + count; offset += count * offsize; return offset - 1; }, getCharString: function (data, offset, o) { - var bin = Typr["B"]; + var bin = Typr['B']; - var b0 = data[offset], b1 = data[offset + 1], b2 = data[offset + 2], b3 = data[offset + 3], b4 = data[offset + 4]; + var b0 = data[offset], + b1 = data[offset + 1], + b2 = data[offset + 2], + b3 = data[offset + 3], + b4 = data[offset + 4]; var vs = 1; - var op = null, val = null; + var op = null, + val = null; // operand - if (b0 <= 20) { op = b0; vs = 1; } - if (b0 == 12) { op = b0 * 100 + b1; vs = 2; } + if (b0 <= 20) { + op = b0; + vs = 1; + } + if (b0 == 12) { + op = b0 * 100 + b1; + vs = 2; + } //if(b0==19 || b0==20) { op = b0/*+" "+b1*/; vs=2; } - if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; } - if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; } - if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; } - if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; } - if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; } - if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; } - if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; } - - o.val = val != null ? val : "o" + op; + if (21 <= b0 && b0 <= 27) { + op = b0; + vs = 1; + } + if (b0 == 28) { + val = bin.readShort(data, offset + 1); + vs = 3; + } + if (29 <= b0 && b0 <= 31) { + op = b0; + vs = 1; + } + if (32 <= b0 && b0 <= 246) { + val = b0 - 139; + vs = 1; + } + if (247 <= b0 && b0 <= 250) { + val = (b0 - 247) * 256 + b1 + 108; + vs = 2; + } + if (251 <= b0 && b0 <= 254) { + val = -(b0 - 251) * 256 - b1 - 108; + vs = 2; + } + if (b0 == 255) { + val = bin.readInt(data, offset + 1) / 0xffff; + vs = 5; + } + + o.val = val != null ? val : 'o' + op; o.size = vs; }, readCharString: function (data, offset, length) { var end = offset + length; - var bin = Typr["B"]; + var bin = Typr['B']; var arr = []; while (offset < end) { - var b0 = data[offset], b1 = data[offset + 1], b2 = data[offset + 2], b3 = data[offset + 3], b4 = data[offset + 4]; + var b0 = data[offset], + b1 = data[offset + 1], + b2 = data[offset + 2], + b3 = data[offset + 3], + b4 = data[offset + 4]; var vs = 1; - var op = null, val = null; + var op = null, + val = null; // operand - if (b0 <= 20) { op = b0; vs = 1; } - if (b0 == 12) { op = b0 * 100 + b1; vs = 2; } - if (b0 == 19 || b0 == 20) { op = b0/*+" "+b1*/; vs = 2; } - if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; } - if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; } - if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; } - if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; } - if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; } - if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; } - if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; } - - arr.push(val != null ? val : "o" + op); + if (b0 <= 20) { + op = b0; + vs = 1; + } + if (b0 == 12) { + op = b0 * 100 + b1; + vs = 2; + } + if (b0 == 19 || b0 == 20) { + op = b0; /*+" "+b1*/ + vs = 2; + } + if (21 <= b0 && b0 <= 27) { + op = b0; + vs = 1; + } + if (b0 == 28) { + val = bin.readShort(data, offset + 1); + vs = 3; + } + if (29 <= b0 && b0 <= 31) { + op = b0; + vs = 1; + } + if (32 <= b0 && b0 <= 246) { + val = b0 - 139; + vs = 1; + } + if (247 <= b0 && b0 <= 250) { + val = (b0 - 247) * 256 + b1 + 108; + vs = 2; + } + if (251 <= b0 && b0 <= 254) { + val = -(b0 - 251) * 256 - b1 - 108; + vs = 2; + } + if (b0 == 255) { + val = bin.readInt(data, offset + 1) / 0xffff; + vs = 5; + } + + arr.push(val != null ? val : 'o' + op); offset += vs; //var cv = arr[arr.length-1]; @@ -655,57 +773,163 @@ Typr["T"].CFF = { }, readDict: function (data, offset, end) { - var bin = Typr["B"]; + var bin = Typr['B']; //var dict = []; var dict = {}; var carr = []; while (offset < end) { - var b0 = data[offset], b1 = data[offset + 1], b2 = data[offset + 2], b3 = data[offset + 3], b4 = data[offset + 4]; + var b0 = data[offset], + b1 = data[offset + 1], + b2 = data[offset + 2], + b3 = data[offset + 3], + b4 = data[offset + 4]; var vs = 1; - var key = null, val = null; + var key = null, + val = null; // operand - if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; } - if (b0 == 29) { val = bin.readInt(data, offset + 1); vs = 5; } - if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; } - if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; } - if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; } - if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; throw "unknown number"; } + if (b0 == 28) { + val = bin.readShort(data, offset + 1); + vs = 3; + } + if (b0 == 29) { + val = bin.readInt(data, offset + 1); + vs = 5; + } + if (32 <= b0 && b0 <= 246) { + val = b0 - 139; + vs = 1; + } + if (247 <= b0 && b0 <= 250) { + val = (b0 - 247) * 256 + b1 + 108; + vs = 2; + } + if (251 <= b0 && b0 <= 254) { + val = -(b0 - 251) * 256 - b1 - 108; + vs = 2; + } + if (b0 == 255) { + val = bin.readInt(data, offset + 1) / 0xffff; + vs = 5; + throw 'unknown number'; + } if (b0 == 30) { var nibs = []; vs = 1; while (true) { - var b = data[offset + vs]; vs++; - var nib0 = b >> 4, nib1 = b & 0xf; - if (nib0 != 0xf) nibs.push(nib0); if (nib1 != 0xf) nibs.push(nib1); + var b = data[offset + vs]; + vs++; + var nib0 = b >> 4, + nib1 = b & 0xf; + if (nib0 != 0xf) nibs.push(nib0); + if (nib1 != 0xf) nibs.push(nib1); if (nib1 == 0xf) break; } - var s = ""; - var chars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ".", "e", "e-", "reserved", "-", "endOfNumber"]; + var s = ''; + var chars = [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + '.', + 'e', + 'e-', + 'reserved', + '-', + 'endOfNumber' + ]; for (var i = 0; i < nibs.length; i++) s += chars[nibs[i]]; //console.log(nibs); val = parseFloat(s); } - if (b0 <= 21) // operator + if (b0 <= 21) // operator { - var keys = ["version", "Notice", "FullName", "FamilyName", "Weight", "FontBBox", "BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues", - "StdHW", "StdVW", "escape", "UniqueID", "XUID", "charset", "Encoding", "CharStrings", "Private", "Subrs", - "defaultWidthX", "nominalWidthX"]; - - key = keys[b0]; vs = 1; + var keys = [ + 'version', + 'Notice', + 'FullName', + 'FamilyName', + 'Weight', + 'FontBBox', + 'BlueValues', + 'OtherBlues', + 'FamilyBlues', + 'FamilyOtherBlues', + 'StdHW', + 'StdVW', + 'escape', + 'UniqueID', + 'XUID', + 'charset', + 'Encoding', + 'CharStrings', + 'Private', + 'Subrs', + 'defaultWidthX', + 'nominalWidthX' + ]; + + key = keys[b0]; + vs = 1; if (b0 == 12) { - var keys = ["Copyright", "isFixedPitch", "ItalicAngle", "UnderlinePosition", "UnderlineThickness", "PaintType", "CharstringType", "FontMatrix", "StrokeWidth", "BlueScale", - "BlueShift", "BlueFuzz", "StemSnapH", "StemSnapV", "ForceBold", "", "", "LanguageGroup", "ExpansionFactor", "initialRandomSeed", - "SyntheticBase", "PostScript", "BaseFontName", "BaseFontBlend", "", "", "", "", "", "", - "ROS", "CIDFontVersion", "CIDFontRevision", "CIDFontType", "CIDCount", "UIDBase", "FDArray", "FDSelect", "FontName"]; - key = keys[b1]; vs = 2; + var keys = [ + 'Copyright', + 'isFixedPitch', + 'ItalicAngle', + 'UnderlinePosition', + 'UnderlineThickness', + 'PaintType', + 'CharstringType', + 'FontMatrix', + 'StrokeWidth', + 'BlueScale', + 'BlueShift', + 'BlueFuzz', + 'StemSnapH', + 'StemSnapV', + 'ForceBold', + '', + '', + 'LanguageGroup', + 'ExpansionFactor', + 'initialRandomSeed', + 'SyntheticBase', + 'PostScript', + 'BaseFontName', + 'BaseFontBlend', + '', + '', + '', + '', + '', + '', + 'ROS', + 'CIDFontVersion', + 'CIDFontRevision', + 'CIDFontType', + 'CIDCount', + 'UIDBase', + 'FDArray', + 'FDSelect', + 'FontName' + ]; + key = keys[b1]; + vs = 2; } } - if (key != null) { dict[key] = carr.length == 1 ? carr[0] : carr; carr = []; } - else carr.push(val); + if (key != null) { + dict[key] = carr.length == 1 ? carr[0] : carr; + carr = []; + } else carr.push(val); offset += vs; } @@ -713,33 +937,37 @@ Typr["T"].CFF = { } }; - -Typr["T"].cmap = { +Typr['T'].cmap = { parseTab: function (data, offset, length) { var obj = { tables: [], ids: {}, off: offset }; data = new Uint8Array(data.buffer, offset, length); offset = 0; var offset0 = offset; - var bin = Typr["B"], rU = bin.readUshort, cmap = Typr["T"].cmap; - var version = rU(data, offset); offset += 2; - var numTables = rU(data, offset); offset += 2; + var bin = Typr['B'], + rU = bin.readUshort, + cmap = Typr['T'].cmap; + var version = rU(data, offset); + offset += 2; + var numTables = rU(data, offset); + offset += 2; //console.log(version, numTables); var offs = []; - for (var i = 0; i < numTables; i++) { - var platformID = rU(data, offset); offset += 2; - var encodingID = rU(data, offset); offset += 2; - var noffset = bin.readUint(data, offset); offset += 4; + var platformID = rU(data, offset); + offset += 2; + var encodingID = rU(data, offset); + offset += 2; + var noffset = bin.readUint(data, offset); + offset += 4; - var id = "p" + platformID + "e" + encodingID; + var id = 'p' + platformID + 'e' + encodingID; //console.log("cmap subtable", platformID, encodingID, noffset); - var tind = offs.indexOf(noffset); if (tind == -1) { @@ -747,7 +975,7 @@ Typr["T"].cmap = { var subt = {}; offs.push(noffset); //var time = Date.now(); - var format = subt.format = rU(data, noffset); + var format = (subt.format = rU(data, noffset)); if (format == 0) subt = cmap.parse0(data, noffset, subt); //else if(format== 2) subt.off = noffset; else if (format == 4) subt = cmap.parse4(data, noffset, subt); @@ -758,66 +986,94 @@ Typr["T"].cmap = { obj.tables.push(subt); } - if (obj.ids[id] != null) console.log("multiple tables for one platform+encoding: " + id); + if (obj.ids[id] != null) + console.log('multiple tables for one platform+encoding: ' + id); obj.ids[id] = tind; } return obj; }, parse0: function (data, offset, obj) { - var bin = Typr["B"]; + var bin = Typr['B']; + offset += 2; + var len = bin.readUshort(data, offset); + offset += 2; + var lang = bin.readUshort(data, offset); offset += 2; - var len = bin.readUshort(data, offset); offset += 2; - var lang = bin.readUshort(data, offset); offset += 2; obj.map = []; for (var i = 0; i < len - 6; i++) obj.map.push(data[offset + i]); return obj; }, parse4: function (data, offset, obj) { - var bin = Typr["B"], rU = bin.readUshort, rUs = bin.readUshorts; + var bin = Typr['B'], + rU = bin.readUshort, + rUs = bin.readUshorts; var offset0 = offset; offset += 2; - var length = rU(data, offset); offset += 2; - var language = rU(data, offset); offset += 2; - var segCountX2 = rU(data, offset); offset += 2; + var length = rU(data, offset); + offset += 2; + var language = rU(data, offset); + offset += 2; + var segCountX2 = rU(data, offset); + offset += 2; var segCount = segCountX2 >>> 1; - obj.searchRange = rU(data, offset); offset += 2; - obj.entrySelector = rU(data, offset); offset += 2; - obj.rangeShift = rU(data, offset); offset += 2; - obj.endCount = rUs(data, offset, segCount); offset += segCount * 2; + obj.searchRange = rU(data, offset); + offset += 2; + obj.entrySelector = rU(data, offset); + offset += 2; + obj.rangeShift = rU(data, offset); offset += 2; - obj.startCount = rUs(data, offset, segCount); offset += segCount * 2; + obj.endCount = rUs(data, offset, segCount); + offset += segCount * 2; + offset += 2; + obj.startCount = rUs(data, offset, segCount); + offset += segCount * 2; obj.idDelta = []; - for (var i = 0; i < segCount; i++) { obj.idDelta.push(bin.readShort(data, offset)); offset += 2; } - obj.idRangeOffset = rUs(data, offset, segCount); offset += segCount * 2; - obj.glyphIdArray = rUs(data, offset, ((offset0 + length) - offset) >> 1); //offset += segCount*2; + for (var i = 0; i < segCount; i++) { + obj.idDelta.push(bin.readShort(data, offset)); + offset += 2; + } + obj.idRangeOffset = rUs(data, offset, segCount); + offset += segCount * 2; + obj.glyphIdArray = rUs(data, offset, (offset0 + length - offset) >> 1); //offset += segCount*2; return obj; }, parse6: function (data, offset, obj) { - var bin = Typr["B"]; + var bin = Typr['B']; var offset0 = offset; offset += 2; - var length = bin.readUshort(data, offset); offset += 2; - var language = bin.readUshort(data, offset); offset += 2; - obj.firstCode = bin.readUshort(data, offset); offset += 2; - var entryCount = bin.readUshort(data, offset); offset += 2; + var length = bin.readUshort(data, offset); + offset += 2; + var language = bin.readUshort(data, offset); + offset += 2; + obj.firstCode = bin.readUshort(data, offset); + offset += 2; + var entryCount = bin.readUshort(data, offset); + offset += 2; obj.glyphIdArray = []; - for (var i = 0; i < entryCount; i++) { obj.glyphIdArray.push(bin.readUshort(data, offset)); offset += 2; } + for (var i = 0; i < entryCount; i++) { + obj.glyphIdArray.push(bin.readUshort(data, offset)); + offset += 2; + } return obj; }, parse12: function (data, offset, obj) { - var bin = Typr["B"], rU = bin.readUint; + var bin = Typr['B'], + rU = bin.readUint; var offset0 = offset; offset += 4; - var length = rU(data, offset); offset += 4; - var lang = rU(data, offset); offset += 4; - var nGroups = rU(data, offset) * 3; offset += 4; + var length = rU(data, offset); + offset += 4; + var lang = rU(data, offset); + offset += 4; + var nGroups = rU(data, offset) * 3; + offset += 4; - var gps = obj.groups = new Uint32Array(nGroups);//new Uint32Array(data.slice(offset, offset+nGroups*12).buffer); + var gps = (obj.groups = new Uint32Array(nGroups)); //new Uint32Array(data.slice(offset, offset+nGroups*12).buffer); for (var i = 0; i < nGroups; i += 3) { gps[i] = rU(data, offset + (i << 2)); @@ -828,26 +1084,35 @@ Typr["T"].cmap = { } }; -Typr["T"].CBLC = { +Typr['T'].CBLC = { parseTab: function (data, offset, length) { - var bin = Typr["B"], ooff = offset; + var bin = Typr['B'], + ooff = offset; - var maj = bin.readUshort(data, offset); offset += 2; - var min = bin.readUshort(data, offset); offset += 2; + var maj = bin.readUshort(data, offset); + offset += 2; + var min = bin.readUshort(data, offset); + offset += 2; - var numSizes = bin.readUint(data, offset); offset += 4; + var numSizes = bin.readUint(data, offset); + offset += 4; var out = []; for (var i = 0; i < numSizes; i++) { - var off = bin.readUint(data, offset); offset += 4; // indexSubTableArrayOffset - var siz = bin.readUint(data, offset); offset += 4; // indexTablesSize - var num = bin.readUint(data, offset); offset += 4; // numberOfIndexSubTables + var off = bin.readUint(data, offset); + offset += 4; // indexSubTableArrayOffset + var siz = bin.readUint(data, offset); + offset += 4; // indexTablesSize + var num = bin.readUint(data, offset); + offset += 4; // numberOfIndexSubTables offset += 4; offset += 2 * 12; - var sGlyph = bin.readUshort(data, offset); offset += 2; - var eGlyph = bin.readUshort(data, offset); offset += 2; + var sGlyph = bin.readUshort(data, offset); + offset += 2; + var eGlyph = bin.readUshort(data, offset); + offset += 2; //console.log(off,siz,num, sGlyph, eGlyph); @@ -855,21 +1120,29 @@ Typr["T"].CBLC = { var coff = ooff + off; for (var j = 0; j < 3; j++) { - var fgI = bin.readUshort(data, coff); coff += 2; - var lgI = bin.readUshort(data, coff); coff += 2; - var nxt = bin.readUint(data, coff); coff += 4; + var fgI = bin.readUshort(data, coff); + coff += 2; + var lgI = bin.readUshort(data, coff); + coff += 2; + var nxt = bin.readUint(data, coff); + coff += 4; var gcnt = lgI - fgI + 1; //console.log(fgI, lgI, nxt); //if(nxt==0) break; var ioff = ooff + off + nxt; - var inF = bin.readUshort(data, ioff); ioff += 2; if (inF != 1) throw inF; - var imF = bin.readUshort(data, ioff); ioff += 2; - var imgo = bin.readUint(data, ioff); ioff += 4; + var inF = bin.readUshort(data, ioff); + ioff += 2; + if (inF != 1) throw inF; + var imF = bin.readUshort(data, ioff); + ioff += 2; + var imgo = bin.readUint(data, ioff); + ioff += 4; var oarr = []; for (var gi = 0; gi < gcnt; gi++) { - var sbitO = bin.readUint(data, ioff + gi * 4); oarr.push(imgo + sbitO); + var sbitO = bin.readUint(data, ioff + gi * 4); + oarr.push(imgo + sbitO); //console.log("--",sbitO); } out.push([fgI, lgI, imF, oarr]); @@ -879,9 +1152,9 @@ Typr["T"].CBLC = { } }; -Typr["T"].CBDT = { +Typr['T'].CBDT = { parseTab: function (data, offset, length) { - var bin = Typr["B"]; + var bin = Typr['B']; var ooff = offset; //var maj = bin.readUshort(data,offset); offset+=2; @@ -891,72 +1164,106 @@ Typr["T"].CBDT = { } }; -Typr["T"].glyf = { +Typr['T'].glyf = { parseTab: function (data, offset, length, font) { - var obj = [], ng = font["maxp"]["numGlyphs"]; + var obj = [], + ng = font['maxp']['numGlyphs']; for (var g = 0; g < ng; g++) obj.push(null); return obj; }, _parseGlyf: function (font, g) { - var bin = Typr["B"]; - var data = font["_data"], loca = font["loca"]; + var bin = Typr['B']; + var data = font['_data'], + loca = font['loca']; if (loca[g] == loca[g + 1]) return null; - var offset = Typr["findTable"](data, "glyf", font["_offset"])[0] + loca[g]; + var offset = Typr['findTable'](data, 'glyf', font['_offset'])[0] + loca[g]; var gl = {}; - gl.noc = bin.readShort(data, offset); offset += 2; // number of contours - gl.xMin = bin.readShort(data, offset); offset += 2; - gl.yMin = bin.readShort(data, offset); offset += 2; - gl.xMax = bin.readShort(data, offset); offset += 2; - gl.yMax = bin.readShort(data, offset); offset += 2; + gl.noc = bin.readShort(data, offset); + offset += 2; // number of contours + gl.xMin = bin.readShort(data, offset); + offset += 2; + gl.yMin = bin.readShort(data, offset); + offset += 2; + gl.xMax = bin.readShort(data, offset); + offset += 2; + gl.yMax = bin.readShort(data, offset); + offset += 2; if (gl.xMin >= gl.xMax || gl.yMin >= gl.yMax) return null; if (gl.noc > 0) { gl.endPts = []; - for (var i = 0; i < gl.noc; i++) { gl.endPts.push(bin.readUshort(data, offset)); offset += 2; } + for (var i = 0; i < gl.noc; i++) { + gl.endPts.push(bin.readUshort(data, offset)); + offset += 2; + } - var instructionLength = bin.readUshort(data, offset); offset += 2; - if ((data.length - offset) < instructionLength) return null; - gl.instructions = bin.readBytes(data, offset, instructionLength); offset += instructionLength; + var instructionLength = bin.readUshort(data, offset); + offset += 2; + if (data.length - offset < instructionLength) return null; + gl.instructions = bin.readBytes(data, offset, instructionLength); + offset += instructionLength; var crdnum = gl.endPts[gl.noc - 1] + 1; gl.flags = []; for (var i = 0; i < crdnum; i++) { - var flag = data[offset]; offset++; + var flag = data[offset]; + offset++; gl.flags.push(flag); if ((flag & 8) != 0) { - var rep = data[offset]; offset++; - for (var j = 0; j < rep; j++) { gl.flags.push(flag); i++; } + var rep = data[offset]; + offset++; + for (var j = 0; j < rep; j++) { + gl.flags.push(flag); + i++; + } } } gl.xs = []; for (var i = 0; i < crdnum; i++) { - var i8 = ((gl.flags[i] & 2) != 0), same = ((gl.flags[i] & 16) != 0); - if (i8) { gl.xs.push(same ? data[offset] : -data[offset]); offset++; } - else { + var i8 = (gl.flags[i] & 2) != 0, + same = (gl.flags[i] & 16) != 0; + if (i8) { + gl.xs.push(same ? data[offset] : -data[offset]); + offset++; + } else { if (same) gl.xs.push(0); - else { gl.xs.push(bin.readShort(data, offset)); offset += 2; } + else { + gl.xs.push(bin.readShort(data, offset)); + offset += 2; + } } } gl.ys = []; for (var i = 0; i < crdnum; i++) { - var i8 = ((gl.flags[i] & 4) != 0), same = ((gl.flags[i] & 32) != 0); - if (i8) { gl.ys.push(same ? data[offset] : -data[offset]); offset++; } - else { + var i8 = (gl.flags[i] & 4) != 0, + same = (gl.flags[i] & 32) != 0; + if (i8) { + gl.ys.push(same ? data[offset] : -data[offset]); + offset++; + } else { if (same) gl.ys.push(0); - else { gl.ys.push(bin.readShort(data, offset)); offset += 2; } + else { + gl.ys.push(bin.readShort(data, offset)); + offset += 2; + } } } - var x = 0, y = 0; - for (var i = 0; i < crdnum; i++) { x += gl.xs[i]; y += gl.ys[i]; gl.xs[i] = x; gl.ys[i] = y; } + var x = 0, + y = 0; + for (var i = 0; i < crdnum; i++) { + x += gl.xs[i]; + y += gl.ys[i]; + gl.xs[i] = x; + gl.ys[i] = y; + } //console.log(endPtsOfContours, instructionLength, instructions, flags, xCoordinates, yCoordinates); - } - else { + } else { var ARG_1_AND_2_ARE_WORDS = 1 << 0; var ARGS_ARE_XY_VALUES = 1 << 1; var ROUND_XY_TO_GRID = 1 << 2; @@ -974,123 +1281,199 @@ Typr["T"].glyf = { gl.parts = []; var flags; do { - flags = bin.readUshort(data, offset); offset += 2; - var part = { m: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 }, p1: -1, p2: -1 }; gl.parts.push(part); - part.glyphIndex = bin.readUshort(data, offset); offset += 2; + flags = bin.readUshort(data, offset); + offset += 2; + var part = { + m: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 }, + p1: -1, + p2: -1 + }; + gl.parts.push(part); + part.glyphIndex = bin.readUshort(data, offset); + offset += 2; if (flags & ARG_1_AND_2_ARE_WORDS) { - var arg1 = bin.readShort(data, offset); offset += 2; - var arg2 = bin.readShort(data, offset); offset += 2; + var arg1 = bin.readShort(data, offset); + offset += 2; + var arg2 = bin.readShort(data, offset); + offset += 2; } else { - var arg1 = bin.readInt8(data, offset); offset++; - var arg2 = bin.readInt8(data, offset); offset++; + var arg1 = bin.readInt8(data, offset); + offset++; + var arg2 = bin.readInt8(data, offset); + offset++; } - if (flags & ARGS_ARE_XY_VALUES) { part.m.tx = arg1; part.m.ty = arg2; } - else { part.p1 = arg1; part.p2 = arg2; } + if (flags & ARGS_ARE_XY_VALUES) { + part.m.tx = arg1; + part.m.ty = arg2; + } else { + part.p1 = arg1; + part.p2 = arg2; + } //part.m.tx = arg1; part.m.ty = arg2; //else { throw "params are not XY values"; } if (flags & WE_HAVE_A_SCALE) { - part.m.a = part.m.d = bin.readF2dot14(data, offset); offset += 2; + part.m.a = part.m.d = bin.readF2dot14(data, offset); + offset += 2; } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { - part.m.a = bin.readF2dot14(data, offset); offset += 2; - part.m.d = bin.readF2dot14(data, offset); offset += 2; + part.m.a = bin.readF2dot14(data, offset); + offset += 2; + part.m.d = bin.readF2dot14(data, offset); + offset += 2; } else if (flags & WE_HAVE_A_TWO_BY_TWO) { - part.m.a = bin.readF2dot14(data, offset); offset += 2; - part.m.b = bin.readF2dot14(data, offset); offset += 2; - part.m.c = bin.readF2dot14(data, offset); offset += 2; - part.m.d = bin.readF2dot14(data, offset); offset += 2; + part.m.a = bin.readF2dot14(data, offset); + offset += 2; + part.m.b = bin.readF2dot14(data, offset); + offset += 2; + part.m.c = bin.readF2dot14(data, offset); + offset += 2; + part.m.d = bin.readF2dot14(data, offset); + offset += 2; } - } while (flags & MORE_COMPONENTS) + } while (flags & MORE_COMPONENTS); if (flags & WE_HAVE_INSTRUCTIONS) { - var numInstr = bin.readUshort(data, offset); offset += 2; + var numInstr = bin.readUshort(data, offset); + offset += 2; gl.instr = []; - for (var i = 0; i < numInstr; i++) { gl.instr.push(data[offset]); offset++; } + for (var i = 0; i < numInstr; i++) { + gl.instr.push(data[offset]); + offset++; + } } } return gl; } }; -Typr["T"].head = { +Typr['T'].head = { parseTab: function (data, offset, length) { - var bin = Typr["B"]; + var bin = Typr['B']; var obj = {}; - var tableVersion = bin.readFixed(data, offset); offset += 4; - - obj["fontRevision"] = bin.readFixed(data, offset); offset += 4; - var checkSumAdjustment = bin.readUint(data, offset); offset += 4; - var magicNumber = bin.readUint(data, offset); offset += 4; - obj["flags"] = bin.readUshort(data, offset); offset += 2; - obj["unitsPerEm"] = bin.readUshort(data, offset); offset += 2; - obj["created"] = bin.readUint64(data, offset); offset += 8; - obj["modified"] = bin.readUint64(data, offset); offset += 8; - obj["xMin"] = bin.readShort(data, offset); offset += 2; - obj["yMin"] = bin.readShort(data, offset); offset += 2; - obj["xMax"] = bin.readShort(data, offset); offset += 2; - obj["yMax"] = bin.readShort(data, offset); offset += 2; - obj["macStyle"] = bin.readUshort(data, offset); offset += 2; - obj["lowestRecPPEM"] = bin.readUshort(data, offset); offset += 2; - obj["fontDirectionHint"] = bin.readShort(data, offset); offset += 2; - obj["indexToLocFormat"] = bin.readShort(data, offset); offset += 2; - obj["glyphDataFormat"] = bin.readShort(data, offset); offset += 2; + var tableVersion = bin.readFixed(data, offset); + offset += 4; + + obj['fontRevision'] = bin.readFixed(data, offset); + offset += 4; + var checkSumAdjustment = bin.readUint(data, offset); + offset += 4; + var magicNumber = bin.readUint(data, offset); + offset += 4; + obj['flags'] = bin.readUshort(data, offset); + offset += 2; + obj['unitsPerEm'] = bin.readUshort(data, offset); + offset += 2; + obj['created'] = bin.readUint64(data, offset); + offset += 8; + obj['modified'] = bin.readUint64(data, offset); + offset += 8; + obj['xMin'] = bin.readShort(data, offset); + offset += 2; + obj['yMin'] = bin.readShort(data, offset); + offset += 2; + obj['xMax'] = bin.readShort(data, offset); + offset += 2; + obj['yMax'] = bin.readShort(data, offset); + offset += 2; + obj['macStyle'] = bin.readUshort(data, offset); + offset += 2; + obj['lowestRecPPEM'] = bin.readUshort(data, offset); + offset += 2; + obj['fontDirectionHint'] = bin.readShort(data, offset); + offset += 2; + obj['indexToLocFormat'] = bin.readShort(data, offset); + offset += 2; + obj['glyphDataFormat'] = bin.readShort(data, offset); + offset += 2; return obj; } }; -Typr["T"].hhea = { +Typr['T'].hhea = { parseTab: function (data, offset, length) { - var bin = Typr["B"]; + var bin = Typr['B']; var obj = {}; - var tableVersion = bin.readFixed(data, offset); offset += 4; + var tableVersion = bin.readFixed(data, offset); + offset += 4; - var keys = ["ascender", "descender", "lineGap", - "advanceWidthMax", "minLeftSideBearing", "minRightSideBearing", "xMaxExtent", - "caretSlopeRise", "caretSlopeRun", "caretOffset", - "res0", "res1", "res2", "res3", - "metricDataFormat", "numberOfHMetrics"]; + var keys = [ + 'ascender', + 'descender', + 'lineGap', + 'advanceWidthMax', + 'minLeftSideBearing', + 'minRightSideBearing', + 'xMaxExtent', + 'caretSlopeRise', + 'caretSlopeRun', + 'caretOffset', + 'res0', + 'res1', + 'res2', + 'res3', + 'metricDataFormat', + 'numberOfHMetrics' + ]; for (var i = 0; i < keys.length; i++) { var key = keys[i]; - var func = (key == "advanceWidthMax" || key == "numberOfHMetrics") ? bin.readUshort : bin.readShort; + var func = + key == 'advanceWidthMax' || key == 'numberOfHMetrics' + ? bin.readUshort + : bin.readShort; obj[key] = func(data, offset + i * 2); } return obj; } }; - -Typr["T"].hmtx = { +Typr['T'].hmtx = { parseTab: function (data, offset, length, font) { - var bin = Typr["B"]; + var bin = Typr['B']; var aWidth = []; var lsBearing = []; - var nG = font["maxp"]["numGlyphs"], nH = font["hhea"]["numberOfHMetrics"]; - var aw = 0, lsb = 0, i = 0; - while (i < nH) { aw = bin.readUshort(data, offset + (i << 2)); lsb = bin.readShort(data, offset + (i << 2) + 2); aWidth.push(aw); lsBearing.push(lsb); i++; } - while (i < nG) { aWidth.push(aw); lsBearing.push(lsb); i++; } + var nG = font['maxp']['numGlyphs'], + nH = font['hhea']['numberOfHMetrics']; + var aw = 0, + lsb = 0, + i = 0; + while (i < nH) { + aw = bin.readUshort(data, offset + (i << 2)); + lsb = bin.readShort(data, offset + (i << 2) + 2); + aWidth.push(aw); + lsBearing.push(lsb); + i++; + } + while (i < nG) { + aWidth.push(aw); + lsBearing.push(lsb); + i++; + } return { aWidth: aWidth, lsBearing: lsBearing }; } }; - -Typr["T"].kern = { +Typr['T'].kern = { parseTab: function (data, offset, length, font) { - var bin = Typr["B"], kern = Typr["T"].kern; + var bin = Typr['B'], + kern = Typr['T'].kern; var version = bin.readUshort(data, offset); if (version == 1) return kern.parseV1(data, offset, length, font); - var nTables = bin.readUshort(data, offset + 2); offset += 4; + var nTables = bin.readUshort(data, offset + 2); + offset += 4; var map = { glyph1: [], rval: [] }; for (var i = 0; i < nTables; i++) { - offset += 2; // skip version - var length = bin.readUshort(data, offset); offset += 2; - var coverage = bin.readUshort(data, offset); offset += 2; + offset += 2; // skip version + var length = bin.readUshort(data, offset); + offset += 2; + var coverage = bin.readUshort(data, offset); + offset += 2; var format = coverage >>> 8; - /* I have seen format 128 once, that's why I do */ format &= 0xf; + /* I have seen format 128 once, that's why I do */ format &= 0xf; if (format == 0) offset = kern.readFormat0(data, offset, map); //else throw "unknown kern table format: "+format; } @@ -1098,16 +1481,21 @@ Typr["T"].kern = { }, parseV1: function (data, offset, length, font) { - var bin = Typr["B"], kern = Typr["T"].kern; + var bin = Typr['B'], + kern = Typr['T'].kern; - var version = bin.readFixed(data, offset); // 0x00010000 - var nTables = bin.readUint(data, offset + 4); offset += 8; + var version = bin.readFixed(data, offset); // 0x00010000 + var nTables = bin.readUint(data, offset + 4); + offset += 8; var map = { glyph1: [], rval: [] }; for (var i = 0; i < nTables; i++) { - var length = bin.readUint(data, offset); offset += 4; - var coverage = bin.readUshort(data, offset); offset += 2; - var tupleIndex = bin.readUshort(data, offset); offset += 2; + var length = bin.readUint(data, offset); + offset += 4; + var coverage = bin.readUshort(data, offset); + offset += 2; + var tupleIndex = bin.readUshort(data, offset); + offset += 2; var format = coverage & 0xff; if (format == 0) offset = kern.readFormat0(data, offset, map); //else throw "unknown kern table format: "+format; @@ -1116,53 +1504,67 @@ Typr["T"].kern = { }, readFormat0: function (data, offset, map) { - var bin = Typr["B"], rUs = bin.readUshort; + var bin = Typr['B'], + rUs = bin.readUshort; var pleft = -1; var nPairs = rUs(data, offset); var searchRange = rUs(data, offset + 2); var entrySelector = rUs(data, offset + 4); - var rangeShift = rUs(data, offset + 6); offset += 8; + var rangeShift = rUs(data, offset + 6); + offset += 8; for (var j = 0; j < nPairs; j++) { - var left = rUs(data, offset); offset += 2; - var right = rUs(data, offset); offset += 2; - var value = bin.readShort(data, offset); offset += 2; - if (left != pleft) { map.glyph1.push(left); map.rval.push({ glyph2: [], vals: [] }) } + var left = rUs(data, offset); + offset += 2; + var right = rUs(data, offset); + offset += 2; + var value = bin.readShort(data, offset); + offset += 2; + if (left != pleft) { + map.glyph1.push(left); + map.rval.push({ glyph2: [], vals: [] }); + } var rval = map.rval[map.rval.length - 1]; - rval.glyph2.push(right); rval.vals.push(value); + rval.glyph2.push(right); + rval.vals.push(value); pleft = left; } return offset; } }; - -Typr["T"].loca = { +Typr['T'].loca = { parseTab: function (data, offset, length, font) { - var bin = Typr["B"]; + var bin = Typr['B']; var obj = []; - var ver = font["head"]["indexToLocFormat"]; - var len = font["maxp"]["numGlyphs"] + 1; + var ver = font['head']['indexToLocFormat']; + var len = font['maxp']['numGlyphs'] + 1; - if (ver == 0) for (var i = 0; i < len; i++) obj.push(bin.readUshort(data, offset + (i << 1)) << 1); - if (ver == 1) for (var i = 0; i < len; i++) obj.push(bin.readUint(data, offset + (i << 2))); + if (ver == 0) + for (var i = 0; i < len; i++) + obj.push(bin.readUshort(data, offset + (i << 1)) << 1); + if (ver == 1) + for (var i = 0; i < len; i++) + obj.push(bin.readUint(data, offset + (i << 2))); return obj; } }; - -Typr["T"].maxp = { +Typr['T'].maxp = { parseTab: function (data, offset, length) { //console.log(data.length, offset, length); - var bin = Typr["B"], rU = bin.readUshort; + var bin = Typr['B'], + rU = bin.readUshort; var obj = {}; // both versions 0.5 and 1.0 - var ver = bin.readUint(data, offset); offset += 4; + var ver = bin.readUint(data, offset); + offset += 4; - obj["numGlyphs"] = rU(data, offset); offset += 2; + obj['numGlyphs'] = rU(data, offset); + offset += 2; // only 1.0 /* @@ -1186,81 +1588,95 @@ Typr["T"].maxp = { return obj; } }; -Typr["T"].name = { +Typr['T'].name = { parseTab: function (data, offset, length) { - var bin = Typr["B"]; + var bin = Typr['B']; var obj = {}; - var format = bin.readUshort(data, offset); offset += 2; - var count = bin.readUshort(data, offset); offset += 2; - var stringOffset = bin.readUshort(data, offset); offset += 2; + var format = bin.readUshort(data, offset); + offset += 2; + var count = bin.readUshort(data, offset); + offset += 2; + var stringOffset = bin.readUshort(data, offset); + offset += 2; var ooo = offset - 6 + stringOffset; //console.log(format,count); var names = [ - "copyright", - "fontFamily", - "fontSubfamily", - "ID", - "fullName", - "version", - "postScriptName", - "trademark", - "manufacturer", - "designer", - "description", - "urlVendor", - "urlDesigner", - "licence", - "licenceURL", - "---", - "typoFamilyName", - "typoSubfamilyName", - "compatibleFull", - "sampleText", - "postScriptCID", - "wwsFamilyName", - "wwsSubfamilyName", - "lightPalette", - "darkPalette" + 'copyright', + 'fontFamily', + 'fontSubfamily', + 'ID', + 'fullName', + 'version', + 'postScriptName', + 'trademark', + 'manufacturer', + 'designer', + 'description', + 'urlVendor', + 'urlDesigner', + 'licence', + 'licenceURL', + '---', + 'typoFamilyName', + 'typoSubfamilyName', + 'compatibleFull', + 'sampleText', + 'postScriptCID', + 'wwsFamilyName', + 'wwsSubfamilyName', + 'lightPalette', + 'darkPalette' ]; var rU = bin.readUshort; for (var i = 0; i < count; i++) { - var platformID = rU(data, offset); offset += 2; - var encodingID = rU(data, offset); offset += 2; - var languageID = rU(data, offset); offset += 2; - var nameID = rU(data, offset); offset += 2; - var slen = rU(data, offset); offset += 2; - var noffset = rU(data, offset); offset += 2; + var platformID = rU(data, offset); + offset += 2; + var encodingID = rU(data, offset); + offset += 2; + var languageID = rU(data, offset); + offset += 2; + var nameID = rU(data, offset); + offset += 2; + var slen = rU(data, offset); + offset += 2; + var noffset = rU(data, offset); + offset += 2; //console.log(platformID, encodingID, languageID.toString(16), nameID, length, noffset); - var soff = ooo + noffset; var str; - if (false) { } - else if (platformID == 0) str = bin.readUnicode(data, soff, slen / 2); - else if (platformID == 3 && encodingID == 0) str = bin.readUnicode(data, soff, slen / 2); - else if (platformID == 1 && encodingID == 25) str = bin.readUnicode(data, soff, slen / 2); + if (false) { + } else if (platformID == 0) str = bin.readUnicode(data, soff, slen / 2); + else if (platformID == 3 && encodingID == 0) + str = bin.readUnicode(data, soff, slen / 2); + else if (platformID == 1 && encodingID == 25) + str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 0) str = bin.readASCII(data, soff, slen); else if (encodingID == 1) str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 3) str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 4) str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 5) str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 10) str = bin.readUnicode(data, soff, slen / 2); - - else if (platformID == 1) { str = bin.readASCII(data, soff, slen); console.log("reading unknown MAC encoding " + encodingID + " as ASCII") } - else { - console.log("unknown encoding " + encodingID + ", platformID: " + platformID); + else if (platformID == 1) { + str = bin.readASCII(data, soff, slen); + console.log('reading unknown MAC encoding ' + encodingID + ' as ASCII'); + } else { + console.log( + 'unknown encoding ' + encodingID + ', platformID: ' + platformID + ); str = bin.readASCII(data, soff, slen); } - var tid = "p" + platformID + "," + (languageID).toString(16);//Typr._platforms[platformID]; + var tid = 'p' + platformID + ',' + languageID.toString(16); //Typr._platforms[platformID]; if (obj[tid] == null) obj[tid] = {}; - var name = names[nameID]; if (name == null) name = "_" + nameID; + var name = names[nameID]; + if (name == null) name = '_' + nameID; obj[tid][name] = str; - obj[tid]["_lang"] = languageID; + obj[tid]['_lang'] = languageID; //console.log(tid, obj[tid]); } /* @@ -1274,145 +1690,210 @@ Typr["T"].name = { } } */ - var out = Typr["T"].name.selectOne(obj), ff = "fontFamily"; - if (out[ff] == null) for (var p in obj) if (obj[p][ff] != null) out[ff] = obj[p][ff]; + var out = Typr['T'].name.selectOne(obj), + ff = 'fontFamily'; + if (out[ff] == null) + for (var p in obj) if (obj[p][ff] != null) out[ff] = obj[p][ff]; return out; }, selectOne: function (obj) { //console.log(obj); - var psn = "postScriptName"; - - for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0409) return obj[p]; // United States - for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0000) return obj[p]; // Universal - for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0c0c) return obj[p]; // Canada + var psn = 'postScriptName'; + + for (var p in obj) + if (obj[p][psn] != null && obj[p]['_lang'] == 0x0409) return obj[p]; // United States + for (var p in obj) + if (obj[p][psn] != null && obj[p]['_lang'] == 0x0000) return obj[p]; // Universal + for (var p in obj) + if (obj[p][psn] != null && obj[p]['_lang'] == 0x0c0c) return obj[p]; // Canada for (var p in obj) if (obj[p][psn] != null) return obj[p]; var out; - for (var p in obj) { out = obj[p]; break; } - console.log("returning name table with languageID " + out._lang); - if (out[psn] == null && out["ID"] != null) out[psn] = out["ID"]; + for (var p in obj) { + out = obj[p]; + break; + } + console.log('returning name table with languageID ' + out._lang); + if (out[psn] == null && out['ID'] != null) out[psn] = out['ID']; return out; } -} +}; -Typr["T"].OS2 = { +Typr['T'].OS2 = { parseTab: function (data, offset, length) { - var bin = Typr["B"]; - var ver = bin.readUshort(data, offset); offset += 2; + var bin = Typr['B']; + var ver = bin.readUshort(data, offset); + offset += 2; - var OS2 = Typr["T"].OS2; + var OS2 = Typr['T'].OS2; var obj = {}; if (ver == 0) OS2.version0(data, offset, obj); else if (ver == 1) OS2.version1(data, offset, obj); else if (ver == 2 || ver == 3 || ver == 4) OS2.version2(data, offset, obj); else if (ver == 5) OS2.version5(data, offset, obj); - else throw "unknown OS/2 table version: " + ver; + else throw 'unknown OS/2 table version: ' + ver; return obj; }, version0: function (data, offset, obj) { - var bin = Typr["B"]; - obj["xAvgCharWidth"] = bin.readShort(data, offset); offset += 2; - obj["usWeightClass"] = bin.readUshort(data, offset); offset += 2; - obj["usWidthClass"] = bin.readUshort(data, offset); offset += 2; - obj["fsType"] = bin.readUshort(data, offset); offset += 2; - obj["ySubscriptXSize"] = bin.readShort(data, offset); offset += 2; - obj["ySubscriptYSize"] = bin.readShort(data, offset); offset += 2; - obj["ySubscriptXOffset"] = bin.readShort(data, offset); offset += 2; - obj["ySubscriptYOffset"] = bin.readShort(data, offset); offset += 2; - obj["ySuperscriptXSize"] = bin.readShort(data, offset); offset += 2; - obj["ySuperscriptYSize"] = bin.readShort(data, offset); offset += 2; - obj["ySuperscriptXOffset"] = bin.readShort(data, offset); offset += 2; - obj["ySuperscriptYOffset"] = bin.readShort(data, offset); offset += 2; - obj["yStrikeoutSize"] = bin.readShort(data, offset); offset += 2; - obj["yStrikeoutPosition"] = bin.readShort(data, offset); offset += 2; - obj["sFamilyClass"] = bin.readShort(data, offset); offset += 2; - obj["panose"] = bin.readBytes(data, offset, 10); offset += 10; - obj["ulUnicodeRange1"] = bin.readUint(data, offset); offset += 4; - obj["ulUnicodeRange2"] = bin.readUint(data, offset); offset += 4; - obj["ulUnicodeRange3"] = bin.readUint(data, offset); offset += 4; - obj["ulUnicodeRange4"] = bin.readUint(data, offset); offset += 4; - obj["achVendID"] = bin.readASCII(data, offset, 4); offset += 4; - obj["fsSelection"] = bin.readUshort(data, offset); offset += 2; - obj["usFirstCharIndex"] = bin.readUshort(data, offset); offset += 2; - obj["usLastCharIndex"] = bin.readUshort(data, offset); offset += 2; - obj["sTypoAscender"] = bin.readShort(data, offset); offset += 2; - obj["sTypoDescender"] = bin.readShort(data, offset); offset += 2; - obj["sTypoLineGap"] = bin.readShort(data, offset); offset += 2; - obj["usWinAscent"] = bin.readUshort(data, offset); offset += 2; - obj["usWinDescent"] = bin.readUshort(data, offset); offset += 2; + var bin = Typr['B']; + obj['xAvgCharWidth'] = bin.readShort(data, offset); + offset += 2; + obj['usWeightClass'] = bin.readUshort(data, offset); + offset += 2; + obj['usWidthClass'] = bin.readUshort(data, offset); + offset += 2; + obj['fsType'] = bin.readUshort(data, offset); + offset += 2; + obj['ySubscriptXSize'] = bin.readShort(data, offset); + offset += 2; + obj['ySubscriptYSize'] = bin.readShort(data, offset); + offset += 2; + obj['ySubscriptXOffset'] = bin.readShort(data, offset); + offset += 2; + obj['ySubscriptYOffset'] = bin.readShort(data, offset); + offset += 2; + obj['ySuperscriptXSize'] = bin.readShort(data, offset); + offset += 2; + obj['ySuperscriptYSize'] = bin.readShort(data, offset); + offset += 2; + obj['ySuperscriptXOffset'] = bin.readShort(data, offset); + offset += 2; + obj['ySuperscriptYOffset'] = bin.readShort(data, offset); + offset += 2; + obj['yStrikeoutSize'] = bin.readShort(data, offset); + offset += 2; + obj['yStrikeoutPosition'] = bin.readShort(data, offset); + offset += 2; + obj['sFamilyClass'] = bin.readShort(data, offset); + offset += 2; + obj['panose'] = bin.readBytes(data, offset, 10); + offset += 10; + obj['ulUnicodeRange1'] = bin.readUint(data, offset); + offset += 4; + obj['ulUnicodeRange2'] = bin.readUint(data, offset); + offset += 4; + obj['ulUnicodeRange3'] = bin.readUint(data, offset); + offset += 4; + obj['ulUnicodeRange4'] = bin.readUint(data, offset); + offset += 4; + obj['achVendID'] = bin.readASCII(data, offset, 4); + offset += 4; + obj['fsSelection'] = bin.readUshort(data, offset); + offset += 2; + obj['usFirstCharIndex'] = bin.readUshort(data, offset); + offset += 2; + obj['usLastCharIndex'] = bin.readUshort(data, offset); + offset += 2; + obj['sTypoAscender'] = bin.readShort(data, offset); + offset += 2; + obj['sTypoDescender'] = bin.readShort(data, offset); + offset += 2; + obj['sTypoLineGap'] = bin.readShort(data, offset); + offset += 2; + obj['usWinAscent'] = bin.readUshort(data, offset); + offset += 2; + obj['usWinDescent'] = bin.readUshort(data, offset); + offset += 2; return offset; }, version1: function (data, offset, obj) { - var bin = Typr["B"]; - offset = Typr["T"].OS2.version0(data, offset, obj); + var bin = Typr['B']; + offset = Typr['T'].OS2.version0(data, offset, obj); - obj["ulCodePageRange1"] = bin.readUint(data, offset); offset += 4; - obj["ulCodePageRange2"] = bin.readUint(data, offset); offset += 4; + obj['ulCodePageRange1'] = bin.readUint(data, offset); + offset += 4; + obj['ulCodePageRange2'] = bin.readUint(data, offset); + offset += 4; return offset; }, version2: function (data, offset, obj) { - var bin = Typr["B"], rU = bin.readUshort; - offset = Typr["T"].OS2.version1(data, offset, obj); - - obj["sxHeight"] = bin.readShort(data, offset); offset += 2; - obj["sCapHeight"] = bin.readShort(data, offset); offset += 2; - obj["usDefault"] = rU(data, offset); offset += 2; - obj["usBreak"] = rU(data, offset); offset += 2; - obj["usMaxContext"] = rU(data, offset); offset += 2; + var bin = Typr['B'], + rU = bin.readUshort; + offset = Typr['T'].OS2.version1(data, offset, obj); + + obj['sxHeight'] = bin.readShort(data, offset); + offset += 2; + obj['sCapHeight'] = bin.readShort(data, offset); + offset += 2; + obj['usDefault'] = rU(data, offset); + offset += 2; + obj['usBreak'] = rU(data, offset); + offset += 2; + obj['usMaxContext'] = rU(data, offset); + offset += 2; return offset; }, version5: function (data, offset, obj) { - var rU = Typr["B"].readUshort; - offset = Typr["T"].OS2.version2(data, offset, obj); + var rU = Typr['B'].readUshort; + offset = Typr['T'].OS2.version2(data, offset, obj); - obj["usLowerOpticalPointSize"] = rU(data, offset); offset += 2; - obj["usUpperOpticalPointSize"] = rU(data, offset); offset += 2; + obj['usLowerOpticalPointSize'] = rU(data, offset); + offset += 2; + obj['usUpperOpticalPointSize'] = rU(data, offset); + offset += 2; return offset; } -} +}; -Typr["T"].post = { +Typr['T'].post = { parseTab: function (data, offset, length) { - var bin = Typr["B"]; + var bin = Typr['B']; var obj = {}; - obj["version"] = bin.readFixed(data, offset); offset += 4; - obj["italicAngle"] = bin.readFixed(data, offset); offset += 4; - obj["underlinePosition"] = bin.readShort(data, offset); offset += 2; - obj["underlineThickness"] = bin.readShort(data, offset); offset += 2; + obj['version'] = bin.readFixed(data, offset); + offset += 4; + obj['italicAngle'] = bin.readFixed(data, offset); + offset += 4; + obj['underlinePosition'] = bin.readShort(data, offset); + offset += 2; + obj['underlineThickness'] = bin.readShort(data, offset); + offset += 2; return obj; } }; -Typr["T"].SVG = { +Typr['T'].SVG = { parseTab: function (data, offset, length) { - var bin = Typr["B"]; + var bin = Typr['B']; var obj = { entries: [], svgs: [] }; var offset0 = offset; - var tableVersion = bin.readUshort(data, offset); offset += 2; - var svgDocIndexOffset = bin.readUint(data, offset); offset += 4; - var reserved = bin.readUint(data, offset); offset += 4; + var tableVersion = bin.readUshort(data, offset); + offset += 2; + var svgDocIndexOffset = bin.readUint(data, offset); + offset += 4; + var reserved = bin.readUint(data, offset); + offset += 4; offset = svgDocIndexOffset + offset0; - var numEntries = bin.readUshort(data, offset); offset += 2; + var numEntries = bin.readUshort(data, offset); + offset += 2; for (var i = 0; i < numEntries; i++) { - var startGlyphID = bin.readUshort(data, offset); offset += 2; - var endGlyphID = bin.readUshort(data, offset); offset += 2; - var svgDocOffset = bin.readUint(data, offset); offset += 4; - var svgDocLength = bin.readUint(data, offset); offset += 4; + var startGlyphID = bin.readUshort(data, offset); + offset += 2; + var endGlyphID = bin.readUshort(data, offset); + offset += 2; + var svgDocOffset = bin.readUint(data, offset); + offset += 4; + var svgDocLength = bin.readUint(data, offset); + offset += 4; - var sbuf = new Uint8Array(data.buffer, offset0 + svgDocOffset + svgDocIndexOffset, svgDocLength); - if (sbuf[0] == 0x1f && sbuf[1] == 0x8b && sbuf[2] == 0x08) sbuf = pako["inflate"](sbuf); + var sbuf = new Uint8Array( + data.buffer, + offset0 + svgDocOffset + svgDocIndexOffset, + svgDocLength + ); + if (sbuf[0] == 0x1f && sbuf[1] == 0x8b && sbuf[2] == 0x08) + sbuf = pako['inflate'](sbuf); var svg = bin.readUTF8(sbuf, 0, sbuf.length); for (var f = startGlyphID; f <= endGlyphID; f++) { @@ -1424,12 +1905,11 @@ Typr["T"].SVG = { } }; - -Typr["T"].sbix = { +Typr['T'].sbix = { parseTab: function (data, offset, length, obj) { - var numGlyphs = obj["maxp"]["numGlyphs"]; + var numGlyphs = obj['maxp']['numGlyphs']; var ooff = offset; - var bin = Typr["B"]; + var bin = Typr['B']; //var ver = bin.readUshort(data,offset); offset+=2; //var flg = bin.readUshort(data,offset); offset+=2; @@ -1445,60 +1925,82 @@ Typr["T"].sbix = { for (var gi = 0; gi < numGlyphs; gi++) { var aoff = bin.readUint(data, off + 4 + gi * 4); - var noff = bin.readUint(data, off + 4 + gi * 4 + 4); if (aoff == noff) { out[gi] = null; continue; } + var noff = bin.readUint(data, off + 4 + gi * 4 + 4); + if (aoff == noff) { + out[gi] = null; + continue; + } var go = off + aoff; //var ooX = bin.readUshort(data,go); //var ooY = bin.readUshort(data,go+2); - var tag = bin.readASCII(data, go + 4, 4); if (tag != "png ") throw tag; + var tag = bin.readASCII(data, go + 4, 4); + if (tag != 'png ') throw tag; - out[gi] = new Uint8Array(data.buffer, data.byteOffset + go + 8, noff - aoff - 8); + out[gi] = new Uint8Array( + data.buffer, + data.byteOffset + go + 8, + noff - aoff - 8 + ); } } return out; } }; -Typr["T"].colr = { +Typr['T'].colr = { parseTab: function (data, offset, length) { - var bin = Typr["B"]; + var bin = Typr['B']; var ooff = offset; offset += 2; - var num = bin.readUshort(data, offset); offset += 2; + var num = bin.readUshort(data, offset); + offset += 2; - var boff = bin.readUint(data, offset); offset += 4; - var loff = bin.readUint(data, offset); offset += 4; + var boff = bin.readUint(data, offset); + offset += 4; + var loff = bin.readUint(data, offset); + offset += 4; - var lnum = bin.readUshort(data, offset); offset += 2; + var lnum = bin.readUshort(data, offset); + offset += 2; //console.log(num,boff,loff,lnum); var base = {}; var coff = ooff + boff; for (var i = 0; i < num; i++) { - base["g" + bin.readUshort(data, coff)] = [bin.readUshort(data, coff + 2), bin.readUshort(data, coff + 4)]; + base['g' + bin.readUshort(data, coff)] = [ + bin.readUshort(data, coff + 2), + bin.readUshort(data, coff + 4) + ]; coff += 6; } var lays = []; coff = ooff + loff; for (var i = 0; i < lnum; i++) { - lays.push(bin.readUshort(data, coff), bin.readUshort(data, coff + 2)); coff += 4; + lays.push(bin.readUshort(data, coff), bin.readUshort(data, coff + 2)); + coff += 4; } return [base, lays]; } }; -Typr["T"].cpal = { +Typr['T'].cpal = { parseTab: function (data, offset, length) { - var bin = Typr["B"]; + var bin = Typr['B']; var ooff = offset; - var vsn = bin.readUshort(data, offset); offset += 2; + var vsn = bin.readUshort(data, offset); + offset += 2; if (vsn == 0) { - var ets = bin.readUshort(data, offset); offset += 2; - var pts = bin.readUshort(data, offset); offset += 2; - var tot = bin.readUshort(data, offset); offset += 2; - - var fst = bin.readUint(data, offset); offset += 4; + var ets = bin.readUshort(data, offset); + offset += 2; + var pts = bin.readUshort(data, offset); + offset += 2; + var tot = bin.readUshort(data, offset); + offset += 2; + + var fst = bin.readUint(data, offset); + offset += 4; return new Uint8Array(data.buffer, ooff + fst, tot * 4); /* @@ -1510,34 +2012,42 @@ Typr["T"].cpal = { } console.log(ets,pts,tot); */ - } - else throw vsn;//console.log("unknown color palette",vsn); + } else throw vsn; //console.log("unknown color palette",vsn); } }; -Typr["T"].GSUB = { +Typr['T'].GSUB = { parseTab: function (data, offset, length, obj) { //console.log(obj.name.ID); - var bin = Typr["B"], rU = bin.readUshort, rI = bin.readUint; - + var bin = Typr['B'], + rU = bin.readUshort, + rI = bin.readUint; var off = offset; - var maj = rU(data, off); off += 2; - var min = rU(data, off); off += 2; - var slO = rU(data, off); off += 2; - var flO = rU(data, off); off += 2; - var llO = rU(data, off); off += 2; + var maj = rU(data, off); + off += 2; + var min = rU(data, off); + off += 2; + var slO = rU(data, off); + off += 2; + var flO = rU(data, off); + off += 2; + var llO = rU(data, off); + off += 2; //console.log(maj,min,slO,flO,llO); off = offset + flO; var fmap = {}; - var cnt = rU(data, off); off += 2; + var cnt = rU(data, off); + off += 2; for (var i = 0; i < cnt; i++) { - var tag = bin.readASCII(data, off, 4); off += 4; - var fof = rU(data, off); off += 2; + var tag = bin.readASCII(data, off, 4); + off += 4; + var fof = rU(data, off); + off += 2; fmap[tag] = true; } //console.log(fmap); @@ -1545,18 +2055,22 @@ Typr["T"].GSUB = { } }; -Typr["T"].fvar = { +Typr['T'].fvar = { parseTab: function (data, offset, length, obj) { - var name = obj["name"]; + var name = obj['name']; var off = offset; - var bin = Typr["B"]; - var axes = [], inst = []; + var bin = Typr['B']; + var axes = [], + inst = []; off += 8; - var acnt = bin.readUshort(data, off); off += 2; + var acnt = bin.readUshort(data, off); + off += 2; + off += 2; + var icnt = bin.readUshort(data, off); + off += 2; + var isiz = bin.readUshort(data, off); off += 2; - var icnt = bin.readUshort(data, off); off += 2; - var isiz = bin.readUshort(data, off); off += 2; for (var i = 0; i < acnt; i++) { var tag = bin.readASCII(data, off, 4); @@ -1565,17 +2079,23 @@ Typr["T"].fvar = { var max = bin.readFixed(data, off + 12); var flg = bin.readUshort(data, off + 16); var nid = bin.readUshort(data, off + 18); - axes.push([tag, min, def, max, flg, name["_" + nid]]); + axes.push([tag, min, def, max, flg, name['_' + nid]]); //console.log(tag,min,def,max,flg,nid); off += 20; } for (var i = 0; i < icnt; i++) { - var snid = bin.readUshort(data, off), pnid = null; + var snid = bin.readUshort(data, off), + pnid = null; var flg = bin.readUshort(data, off + 2); - var crd = []; for (var j = 0; j < acnt; j++) crd.push(bin.readFixed(data, off + 4 + j * 4)); + var crd = []; + for (var j = 0; j < acnt; j++) + crd.push(bin.readFixed(data, off + 4 + j * 4)); off += 4 + acnt * 4; - if ((isiz & 3) == 2) { pnid = bin.readUshort(data, off); off += 2; } - inst.push([name["_" + snid], flg, crd, pnid]); + if ((isiz & 3) == 2) { + pnid = bin.readUshort(data, off); + off += 2; + } + inst.push([name['_' + snid], flg, crd, pnid]); //console.log(snid,flg, crd); } @@ -1583,8 +2103,7 @@ Typr["T"].fvar = { } }; -Typr["T"].gvar = (function () { - +Typr['T'].gvar = (function () { var EMBEDDED_PEAK_TUPLE = 0x8000; var INTERMEDIATE_REGION = 0x4000; var PRIVATE_POINT_NUMBERS = 0x2000; @@ -1596,24 +2115,40 @@ Typr["T"].gvar = (function () { var SHARED_POINT_NUMBERS = 0x8000; - var bin = Typr["B"]; + var bin = Typr['B']; function readTuple(data, o, acnt) { - var tup = []; for (var j = 0; j < acnt; j++) tup.push(bin.readF2dot14(data, o + j * 2)); + var tup = []; + for (var j = 0; j < acnt; j++) tup.push(bin.readF2dot14(data, o + j * 2)); return tup; } function readTupleVarHeader(data, off, vcnt, acnt, eoff) { var out = []; for (var j = 0; j < vcnt; j++) { - var dsiz = bin.readUshort(data, off); off += 2; - var tind = bin.readUshort(data, off), flag = tind & 0xf000; tind = tind & 0xfff; off += 2; + var dsiz = bin.readUshort(data, off); + off += 2; + var tind = bin.readUshort(data, off), + flag = tind & 0xf000; + tind = tind & 0xfff; + off += 2; //console.log(j, dsiz,tind, flag.toString(16)); - var peak = null, start = null, end = null; - if (flag & EMBEDDED_PEAK_TUPLE) { peak = readTuple(data, off, acnt); off += acnt * 2; } - if (flag & INTERMEDIATE_REGION) { start = readTuple(data, off, acnt); off += acnt * 2; } - if (flag & INTERMEDIATE_REGION) { end = readTuple(data, off, acnt); off += acnt * 2; } + var peak = null, + start = null, + end = null; + if (flag & EMBEDDED_PEAK_TUPLE) { + peak = readTuple(data, off, acnt); + off += acnt * 2; + } + if (flag & INTERMEDIATE_REGION) { + start = readTuple(data, off, acnt); + off += acnt * 2; + } + if (flag & INTERMEDIATE_REGION) { + end = readTuple(data, off, acnt); + off += acnt * 2; + } out.push([dsiz, tind, flag, start, peak, end]); } return out; @@ -1621,19 +2156,31 @@ Typr["T"].gvar = (function () { // Packed "point" numbers function readPointNumbers(data, off, gid) { - var cnt = data[off]; off++; if (cnt == 0) return [[], off]; - if (127 < cnt) { cnt = ((cnt & 127) << 8) | data[off++]; } + var cnt = data[off]; + off++; + if (cnt == 0) return [[], off]; + if (127 < cnt) { + cnt = ((cnt & 127) << 8) | data[off++]; + } //if(gid==116) console.log("---",cnt); - var pts = [], last = 0; // point number data runs + var pts = [], + last = 0; // point number data runs while (pts.length < cnt) { - var v = data[off]; off++; - var wds = (v & POINTS_ARE_WORDS) != 0; v = (v & 127) + 1; + var v = data[off]; + off++; + var wds = (v & POINTS_ARE_WORDS) != 0; + v = (v & 127) + 1; //if(gid==116) console.log("-",v); for (var i = 0; i < v; i++) { var dif = 0; - if (wds) { dif = bin.readUshort(data, off); off += 2; } - else { dif = data[off]; off++; } + if (wds) { + dif = bin.readUshort(data, off); + off += 2; + } else { + dif = data[off]; + off++; + } //if(gid==116) console.log(dif); last += dif; pts.push(last); @@ -1642,29 +2189,42 @@ Typr["T"].gvar = (function () { //console.log(pts); return [pts, off]; - //throw "e"; } - function parseTab(data, offset, length, obj) { var off = offset + 4; - var acnt = bin.readUshort(data, off); off += 2; - var tcnt = bin.readUshort(data, off); off += 2; - var toff = bin.readUint(data, off); off += 4; - var gcnt = bin.readUshort(data, off); off += 2; - var flgs = bin.readUshort(data, off); off += 2; + var acnt = bin.readUshort(data, off); + off += 2; + var tcnt = bin.readUshort(data, off); + off += 2; + var toff = bin.readUint(data, off); + off += 4; + var gcnt = bin.readUshort(data, off); + off += 2; + var flgs = bin.readUshort(data, off); + off += 2; - var goff = bin.readUint(data, off); off += 4; + var goff = bin.readUint(data, off); + off += 4; // glyphVariationDataOffsets - var offs = []; for (var i = 0; i < gcnt + 1; i++) offs.push(bin.readUint(data, off + i * 4)); - + var offs = []; + for (var i = 0; i < gcnt + 1; i++) + offs.push(bin.readUint(data, off + i * 4)); // sharedTuples - var tups = [], mins = [], maxs = []; off = offset + toff; + var tups = [], + mins = [], + maxs = []; + off = offset + toff; for (var i = 0; i < tcnt; i++) { - var peak = readTuple(data, off + i * acnt * 2, acnt), imin = [], imax = []; tups.push(peak); mins.push(imin); maxs.push(imax); + var peak = readTuple(data, off + i * acnt * 2, acnt), + imin = [], + imax = []; + tups.push(peak); + mins.push(imin); + maxs.push(imax); for (var k = 0; k < acnt; k++) { imin[k] = Math.min(peak[k], 0); imax[k] = Math.max(peak[k], 0); @@ -1682,51 +2242,75 @@ Typr["T"].gvar = (function () { //console.log("-------",i); off = offset + goff + offs[i]; // tupleVariationCount - var vcnt = bin.readUshort(data, off); off += 2; //if((vcnt>>>12)!=0) throw "e"; + var vcnt = bin.readUshort(data, off); + off += 2; //if((vcnt>>>12)!=0) throw "e"; - var snum = vcnt & SHARED_POINT_NUMBERS; vcnt &= 0xfff; + var snum = vcnt & SHARED_POINT_NUMBERS; + vcnt &= 0xfff; // offset to the serialized data - var soff = bin.readUshort(data, off); off += 2; - - var hdr = readTupleVarHeader(data, off, vcnt, acnt, offset + goff + offs[i + 1]); - - var tab = []; tabs.push(tab); + var soff = bin.readUshort(data, off); + off += 2; + + var hdr = readTupleVarHeader( + data, + off, + vcnt, + acnt, + offset + goff + offs[i + 1] + ); + + var tab = []; + tabs.push(tab); // Serialized Data off = offset + goff + offs[i] + soff; var sind = null; if (snum) { var oo = readPointNumbers(data, off, i); - sind = oo[0]; off = oo[1]; + sind = oo[0]; + off = oo[1]; } for (var j = 0; j < vcnt; j++) { - var vr = hdr[j], end = off + vr[0]; //console.log(vr); console.log(data.slice(off,off+vr[0])); + var vr = hdr[j], + end = off + vr[0]; //console.log(vr); console.log(data.slice(off,off+vr[0])); var ind = sind; if (vr[2] & PRIVATE_POINT_NUMBERS) { var oo = readPointNumbers(data, off, i); - ind = oo[0]; off = oo[1]; + ind = oo[0]; + off = oo[1]; } // read packed deltas (delta runs) var ds = []; while (off < end) { - var cb = data[off++]; // control byte; + var cb = data[off++]; // control byte; var cnt = (cb & 0x3f) + 1; - if (cb & DELTAS_ARE_ZERO) { for (var k = 0; k < cnt; k++) ds.push(0); } - else if (cb & DELTAS_ARE_WORDS) { for (var k = 0; k < cnt; k++) ds.push(bin.readShort(data, off + k * 2)); off += cnt * 2; } - else { for (var k = 0; k < cnt; k++) ds.push(i8[off + k]); off += cnt; } + if (cb & DELTAS_ARE_ZERO) { + for (var k = 0; k < cnt; k++) ds.push(0); + } else if (cb & DELTAS_ARE_WORDS) { + for (var k = 0; k < cnt; k++) + ds.push(bin.readShort(data, off + k * 2)); + off += cnt * 2; + } else { + for (var k = 0; k < cnt; k++) ds.push(i8[off + k]); + off += cnt; + } } //if(ind) console.log(ind, ds); var ti = vr[1]; - tab.push([[ - vr[3] ? vr[3] : mins[ti], - vr[4] ? vr[4] : tups[ti], - vr[5] ? vr[5] : maxs[ti] - ], ds, ind.length == 0 ? null : ind]); - - if (ind.length != 0 && ind.length * 2 != ds.length) throw "e"; + tab.push([ + [ + vr[3] ? vr[3] : mins[ti], + vr[4] ? vr[4] : tups[ti], + vr[5] ? vr[5] : maxs[ti] + ], + ds, + ind.length == 0 ? null : ind + ]); + + if (ind.length != 0 && ind.length * 2 != ds.length) throw 'e'; //if(i==116) console.log(ind, ds); } } @@ -1736,20 +2320,25 @@ Typr["T"].gvar = (function () { return { parseTab: parseTab }; })(); -Typr["T"].avar = { +Typr['T'].avar = { parseTab: function (data, offset, length, obj) { var off = offset; - var bin = Typr["B"], out = []; + var bin = Typr['B'], + out = []; off += 6; - var acnt = bin.readUshort(data, off); off += 2; + var acnt = bin.readUshort(data, off); + off += 2; for (var ai = 0; ai < acnt; ai++) { - var cnt = bin.readUshort(data, off); off += 2; - var poly = []; out.push(poly); + var cnt = bin.readUshort(data, off); + off += 2; + var poly = []; + out.push(poly); for (var i = 0; i < cnt; i++) { var x = bin.readF2dot14(data, off); - var y = bin.readF2dot14(data, off + 2); off += 4; + var y = bin.readF2dot14(data, off + 2); + off += 4; poly.push(x, y); } } @@ -1758,42 +2347,57 @@ Typr["T"].avar = { } }; -Typr["T"].HVAR = { +Typr['T'].HVAR = { parseTab: function (data, offset, length, obj) { - var off = offset, oo = offset; - var bin = Typr["B"], out = []; + var off = offset, + oo = offset; + var bin = Typr['B'], + out = []; //console.log(data.slice(off)); off += 4; - var varO = bin.readUint(data, off); off += 4; - var advO = bin.readUint(data, off); off += 4; - var lsbO = bin.readUint(data, off); off += 4; - var rsbO = bin.readUint(data, off); off += 4; + var varO = bin.readUint(data, off); + off += 4; + var advO = bin.readUint(data, off); + off += 4; + var lsbO = bin.readUint(data, off); + off += 4; + var rsbO = bin.readUint(data, off); + off += 4; if (lsbO != 0 || rsbO != 0) throw lsbO; //console.log(varO,advO,lsbO,rsbO); - off = oo + varO; // item variation store + off = oo + varO; // item variation store // ItemVariationStore var ioff = off; - var fmt = bin.readUshort(data, off); off += 2; if (fmt != 1) throw "e"; - var vregO = bin.readUint(data, off); off += 4; + var fmt = bin.readUshort(data, off); + off += 2; + if (fmt != 1) throw 'e'; + var vregO = bin.readUint(data, off); + off += 4; // itemVariationDataCount - var vcnt = bin.readUshort(data, off); off += 2; + var vcnt = bin.readUshort(data, off); + off += 2; - var offs = []; for (var i = 0; i < vcnt; i++) offs.push(bin.readUint(data, off + i * 4)); off += vcnt * 4; //if(offs.length!=1) throw "e"; + var offs = []; + for (var i = 0; i < vcnt; i++) offs.push(bin.readUint(data, off + i * 4)); + off += vcnt * 4; //if(offs.length!=1) throw "e"; //console.log(vregO,vcnt,offs); off = ioff + vregO; - var acnt = bin.readUshort(data, off); off += 2; - var rcnt = bin.readUshort(data, off); off += 2; + var acnt = bin.readUshort(data, off); + off += 2; + var rcnt = bin.readUshort(data, off); + off += 2; var regs = []; for (var i = 0; i < rcnt; i++) { - var crd = [[], [], []]; regs.push(crd); + var crd = [[], [], []]; + regs.push(crd); for (var j = 0; j < acnt; j++) { crd[0].push(bin.readF2dot14(data, off + 0)); crd[1].push(bin.readF2dot14(data, off + 2)); @@ -1803,27 +2407,37 @@ Typr["T"].HVAR = { } //console.log(acnt, rcnt, regs); - var i8 = new Int8Array(data.buffer); var varStore = []; for (var i = 0; i < offs.length; i++) { // ItemVariationData - off = oo + varO + offs[i]; var vdata = []; varStore.push(vdata); - var icnt = bin.readUshort(data, off); off += 2; // itemCount - var dcnt = bin.readUshort(data, off); off += 2; if (dcnt & 0x8000) throw "e"; - var rcnt = bin.readUshort(data, off); off += 2; - var ixs = []; for (var j = 0; j < rcnt; j++) ixs.push(bin.readUshort(data, off + j * 2)); off += rcnt * 2; + off = oo + varO + offs[i]; + var vdata = []; + varStore.push(vdata); + var icnt = bin.readUshort(data, off); + off += 2; // itemCount + var dcnt = bin.readUshort(data, off); + off += 2; + if (dcnt & 0x8000) throw 'e'; + var rcnt = bin.readUshort(data, off); + off += 2; + var ixs = []; + for (var j = 0; j < rcnt; j++) + ixs.push(bin.readUshort(data, off + j * 2)); + off += rcnt * 2; //console.log(icnt,dcnt,rcnt,ixs); //console.log(data.slice(off)); - for (var k = 0; k < icnt; k++) { // deltaSets - var deltaData = []; //vdata.push(deltaData); + for (var k = 0; k < icnt; k++) { + // deltaSets + var deltaData = []; //vdata.push(deltaData); for (var ri = 0; ri < rcnt; ri++) { deltaData.push(ri < dcnt ? bin.readShort(data, off) : i8[off]); off += ri < dcnt ? 2 : 1; - } - var dd = new Array(regs.length); dd.fill(0); vdata.push(dd); + var dd = new Array(regs.length); + dd.fill(0); + vdata.push(dd); for (var j = 0; j < ixs.length; j++) dd[ixs[j]] = deltaData[j]; } } @@ -1832,20 +2446,20 @@ Typr["T"].HVAR = { // VariationRegionList - - - off = oo + advO; // advance widths + off = oo + advO; // advance widths // DeltaSetIndexMap - var fmt = data[off++]; if (fmt != 0) throw "e"; + var fmt = data[off++]; + if (fmt != 0) throw 'e'; var entryFormat = data[off++]; - var mapCount = bin.readUshort(data, off); off += 2; + var mapCount = bin.readUshort(data, off); + off += 2; var INNER_INDEX_BIT_COUNT_MASK = 0x0f; var MAP_ENTRY_SIZE_MASK = 0x30; - var entrySize = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1; //if(entrySize!=1) throw entrySize; + var entrySize = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1; //if(entrySize!=1) throw entrySize; //console.log(fmt, entryFormat, mapCount, entrySize); @@ -1853,9 +2467,14 @@ Typr["T"].HVAR = { for (var i = 0; i < mapCount; i++) { var entry = 0; if (entrySize == 1) entry = data[off++]; - else { entry = bin.readUshort(data, off); off += 2; } - var outerIndex = entry >> ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1); - var innerIndex = entry & ((1 << ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1)) - 1); + else { + entry = bin.readUshort(data, off); + off += 2; + } + var outerIndex = + entry >> ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1); + var innerIndex = + entry & ((1 << ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1)) - 1); //map.push(outerIndex,innerIndex); dfs.push(varStore[outerIndex][innerIndex]); //console.log(outerIndex,innerIndex); @@ -1866,17 +2485,33 @@ Typr["T"].HVAR = { } }; -Typr["U"] = function () { +Typr['U'] = (function () { var P = { - MoveTo: function (p, x, y) { p.cmds.push("M"); p.crds.push(x, y); }, - LineTo: function (p, x, y) { p.cmds.push("L"); p.crds.push(x, y); }, - CurveTo: function (p, a, b, c, d, e, f) { p.cmds.push("C"); p.crds.push(a, b, c, d, e, f); }, - qCurveTo: function (p, a, b, c, d) { p.cmds.push("Q"); p.crds.push(a, b, c, d); }, - ClosePath: function (p) { p.cmds.push("Z"); } - } + MoveTo: function (p, x, y) { + p.cmds.push('M'); + p.crds.push(x, y); + }, + LineTo: function (p, x, y) { + p.cmds.push('L'); + p.crds.push(x, y); + }, + CurveTo: function (p, a, b, c, d, e, f) { + p.cmds.push('C'); + p.crds.push(a, b, c, d, e, f); + }, + qCurveTo: function (p, a, b, c, d) { + p.cmds.push('Q'); + p.crds.push(a, b, c, d); + }, + ClosePath: function (p) { + p.cmds.push('Z'); + } + }; function getGlyphPosition(font, gls, i1, ltr) { - var g1 = gls[i1], g2 = gls[i1 + 1], kern = font["kern"]; + var g1 = gls[i1], + g2 = gls[i1 + 1], + kern = font['kern']; if (kern) { var ind1 = kern.glyph1.indexOf(g1); if (ind1 != -1) { @@ -1889,30 +2524,37 @@ Typr["U"] = function () { } function shape(font, str, prm) { if (prm == null) prm = {}; - var ltr = prm["ltr"], fts = prm["fts"], axs = prm["axs"]; - if (font["fvar"] && axs == null) axs = font["fvar"][1][font["_index"]][2]; + var ltr = prm['ltr'], + fts = prm['fts'], + axs = prm['axs']; + if (font['fvar'] && axs == null) axs = font['fvar'][1][font['_index']][2]; - var HVAR = font["HVAR"]; //console.log(HVAR); - if (axs && HVAR) { axs = _normalizeAxis(font, axs); } //console.log(S,axs); + var HVAR = font['HVAR']; //console.log(HVAR); + if (axs && HVAR) { + axs = _normalizeAxis(font, axs); + } //console.log(S,axs); var gls = []; for (var i = 0; i < str.length; i++) { - var cc = str.codePointAt(i); if (cc > 0xffff) i++; + var cc = str.codePointAt(i); + if (cc > 0xffff) i++; gls.push(codeToGlyph(font, cc)); } var shape = []; - var x = 0, y = 0; + var x = 0, + y = 0; for (var i = 0; i < gls.length; i++) { var padj = getGlyphPosition(font, gls, i, ltr); - var gid = gls[i]; //console.log(gid); - var ax = font["hmtx"].aWidth[gid] + padj[2]; - if (HVAR && HVAR[1][gid]) { //ax+=S*HVAR[1][gid][0]; - var difs = HVAR[1][gid]; //console.log(difs); + var gid = gls[i]; //console.log(gid); + var ax = font['hmtx'].aWidth[gid] + padj[2]; + if (HVAR && HVAR[1][gid]) { + //ax+=S*HVAR[1][gid][0]; + var difs = HVAR[1][gid]; //console.log(difs); for (var j = 0; j < HVAR[0].length; j++) { ax += _interpolate(HVAR[0][j], axs) * difs[j]; } } - shape.push({ "g": gid, "cl": i, "dx": 0, "dy": 0, "ax": ax, "ay": 0 }); + shape.push({ g: gid, cl: i, dx: 0, dy: 0, ax: ax, ay: 0 }); x += ax; } return shape; @@ -1920,38 +2562,56 @@ Typr["U"] = function () { function shapeToPath(font, shape, prm) { var tpath = { cmds: [], crds: [] }; - var x = 0, y = 0, clr, axs; - if (prm) { clr = prm["clr"]; axs = prm["axs"]; } + var x = 0, + y = 0, + clr, + axs; + if (prm) { + clr = prm['clr']; + axs = prm['axs']; + } for (var i = 0; i < shape.length; i++) { - var it = shape[i] - var path = glyphToPath(font, it["g"], false, axs), crds = path["crds"]; + var it = shape[i]; + var path = glyphToPath(font, it['g'], false, axs), + crds = path['crds']; for (var j = 0; j < crds.length; j += 2) { - tpath.crds.push(crds[j] + x + it["dx"]); - tpath.crds.push(crds[j + 1] + y + it["dy"]); + tpath.crds.push(crds[j] + x + it['dx']); + tpath.crds.push(crds[j + 1] + y + it['dy']); } if (clr) tpath.cmds.push(clr); - for (var j = 0; j < path["cmds"].length; j++) tpath.cmds.push(path["cmds"][j]); + for (var j = 0; j < path['cmds'].length; j++) + tpath.cmds.push(path['cmds'][j]); var clen = tpath.cmds.length; - if (clr) if (clen != 0 && tpath.cmds[clen - 1] != "X") tpath.cmds.push("X"); // SVG fonts might contain "X". Then, nothing would stroke non-SVG glyphs. + if (clr) + if (clen != 0 && tpath.cmds[clen - 1] != 'X') tpath.cmds.push('X'); // SVG fonts might contain "X". Then, nothing would stroke non-SVG glyphs. - x += it["ax"]; y += it["ay"]; + x += it['ax']; + y += it['ay']; } - return { "cmds": tpath.cmds, "crds": tpath.crds }; + return { cmds: tpath.cmds, crds: tpath.crds }; } - // find the greatest index with a value <=v function arrSearch(arr, k, v) { - var l = 0, r = ~~(arr.length / k); - while (l + 1 != r) { var mid = l + ((r - l) >>> 1); if (arr[mid * k] <= v) l = mid; else r = mid; } + var l = 0, + r = ~~(arr.length / k); + while (l + 1 != r) { + var mid = l + ((r - l) >>> 1); + if (arr[mid * k] <= v) l = mid; + else r = mid; + } //var mi = 0; for(var i=0; i= tab.map.length) gid = 0; else gid = tab.map[code]; - } - /*else if(fmt==2) { + } /*else if(fmt==2) { var data=font["_data"], off = cmap.off+tab.off+6, bin=Typr["B"]; var shKey = bin.readUshort(data,off + 2*(code>>>8)); var shInd = off + 256*2 + shKey*8; @@ -1994,9 +2669,9 @@ Typr["U"] = function () { //throw "e"; //console.log(tab, bin.readUshort(data,off)); //throw "e"; - }*/ - else if (fmt == 4) { - var ec = tab.endCount; gid = 0; + }*/ else if (fmt == 4) { + var ec = tab.endCount; + gid = 0; if (code <= ec[ec.length - 1]) { // smallest index with code <= value var sind = arrSearch(ec, 1, code); @@ -2004,32 +2679,48 @@ Typr["U"] = function () { if (code >= tab.startCount[sind]) { var gli = 0; - if (tab.idRangeOffset[sind] != 0) gli = tab.glyphIdArray[(code - tab.startCount[sind]) + (tab.idRangeOffset[sind] >> 1) - (tab.idRangeOffset.length - sind)]; + if (tab.idRangeOffset[sind] != 0) + gli = + tab.glyphIdArray[ + code - + tab.startCount[sind] + + (tab.idRangeOffset[sind] >> 1) - + (tab.idRangeOffset.length - sind) + ]; else gli = code + tab.idDelta[sind]; - gid = (gli & 0xFFFF); + gid = gli & 0xffff; } } - } - else if (fmt == 6) { - var off = code - tab.firstCode, arr = tab.glyphIdArray; + } else if (fmt == 6) { + var off = code - tab.firstCode, + arr = tab.glyphIdArray; if (off < 0 || off >= arr.length) gid = 0; else gid = arr[off]; - } - else if (fmt == 12) { - var grp = tab.groups; gid = 0; //console.log(grp); throw "e"; + } else if (fmt == 12) { + var grp = tab.groups; + gid = 0; //console.log(grp); throw "e"; if (code <= grp[grp.length - 2]) { var i = arrSearch(grp, 3, code); - if (grp[i] <= code && code <= grp[i + 1]) { gid = grp[i + 2] + (code - grp[i]); } + if (grp[i] <= code && code <= grp[i + 1]) { + gid = grp[i + 2] + (code - grp[i]); + } } - } - else throw "unknown cmap table format " + tab.format; + } else throw 'unknown cmap table format ' + tab.format; //* - var SVG = font["SVG "], loca = font["loca"]; + var SVG = font['SVG '], + loca = font['loca']; // if the font claims to have a Glyph for a character, but the glyph is empty, and the character is not "white", it is a lie! - if (gid != 0 && font["CFF "] == null && (SVG == null || SVG.entries[gid] == null) && loca && loca[gid] == loca[gid + 1] // loca not present in CFF or SVG fonts - && whm[code] == null) gid = 0; + if ( + gid != 0 && + font['CFF '] == null && + (SVG == null || SVG.entries[gid] == null) && + loca && + loca[gid] == loca[gid + 1] && // loca not present in CFF or SVG fonts + whm[code] == null + ) + gid = 0; //*/ return gid; @@ -2037,101 +2728,137 @@ Typr["U"] = function () { function glyphToPath(font, gid, noColor, axs) { var path = { cmds: [], crds: [] }; - if (font["fvar"]) { - if (axs == null) axs = font["fvar"][1][font["_index"]][2]; + if (font['fvar']) { + if (axs == null) axs = font['fvar'][1][font['_index']][2]; axs = _normalizeAxis(font, axs); } - var SVG = font["SVG "], CFF = font["CFF "], COLR = font["COLR"], CBLC = font["CBLC"], CBDT = font["CBDT"], sbix = font["sbix"], upng = window["UPNG"]; + var SVG = font['SVG '], + CFF = font['CFF '], + COLR = font['COLR'], + CBLC = font['CBLC'], + CBDT = font['CBDT'], + sbix = font['sbix'], + upng = window['UPNG']; var strike = null; - if (CBLC && upng) for (var i = 0; i < CBLC.length; i++) if (CBLC[i][0] <= gid && gid <= CBLC[i][1]) strike = CBLC[i]; + if (CBLC && upng) + for (var i = 0; i < CBLC.length; i++) + if (CBLC[i][0] <= gid && gid <= CBLC[i][1]) strike = CBLC[i]; if (strike || (sbix && sbix[gid])) { - if (strike && strike[2] != 17) throw "not a PNG"; + if (strike && strike[2] != 17) throw 'not a PNG'; - if (font["__tmp"] == null) font["__tmp"] = {}; - var cmd = font["__tmp"]["g" + gid]; + if (font['__tmp'] == null) font['__tmp'] = {}; + var cmd = font['__tmp']['g' + gid]; if (cmd == null) { var bmp, len; - if (sbix) { bmp = sbix[gid]; len = bmp.length; } - else { - var boff = strike[3][gid - strike[0]] + 5; // smallGlyphMetrics - len = (CBDT[boff + 1] << 16) | (CBDT[boff + 2] << 8) | CBDT[boff + 3]; boff += 4; + if (sbix) { + bmp = sbix[gid]; + len = bmp.length; + } else { + var boff = strike[3][gid - strike[0]] + 5; // smallGlyphMetrics + len = (CBDT[boff + 1] << 16) | (CBDT[boff + 2] << 8) | CBDT[boff + 3]; + boff += 4; bmp = new Uint8Array(CBDT.buffer, CBDT.byteOffset + boff, len); } - var str = ""; for (var i = 0; i < len; i++) str += String.fromCharCode(bmp[i]); - cmd = font["__tmp"]["g" + gid] = "data:image/png;base64," + btoa(str); + var str = ''; + for (var i = 0; i < len; i++) str += String.fromCharCode(bmp[i]); + cmd = font['__tmp']['g' + gid] = 'data:image/png;base64,' + btoa(str); } path.cmds.push(cmd); - var upe = font["head"]["unitsPerEm"] * 1.15; - var gw = Math.round(upe), gh = Math.round(upe), dy = Math.round(-gh * 0.15); + var upe = font['head']['unitsPerEm'] * 1.15; + var gw = Math.round(upe), + gh = Math.round(upe), + dy = Math.round(-gh * 0.15); path.crds.push(0, gh + dy, gw, gh + dy, gw, dy, 0, dy); //*/ - } - else if (SVG && SVG.entries[gid]) { + } else if (SVG && SVG.entries[gid]) { var p = SVG.entries[gid]; if (p != null) { - if (typeof p == "number") { + if (typeof p == 'number') { var svg = SVG.svgs[p]; - if (typeof svg == "string") { + if (typeof svg == 'string') { var prsr = new DOMParser(); - var doc = prsr["parseFromString"](svg, "image/svg+xml"); - svg = SVG.svgs[p] = doc.getElementsByTagName("svg")[0]; + var doc = prsr['parseFromString'](svg, 'image/svg+xml'); + svg = SVG.svgs[p] = doc.getElementsByTagName('svg')[0]; } - p = Typr["U"]["SVG"].toPath(svg, gid); SVG.entries[gid] = p; + p = Typr['U']['SVG'].toPath(svg, gid); + SVG.entries[gid] = p; } path = p; } - } - else if (noColor != true && COLR && COLR[0]["g" + gid] && COLR[0]["g" + gid][1] > 1) { - - function toHex(n) { var o = n.toString(16); return (o.length == 1 ? "0" : "") + o; } + } else if ( + noColor != true && + COLR && + COLR[0]['g' + gid] && + COLR[0]['g' + gid][1] > 1 + ) { + function toHex(n) { + var o = n.toString(16); + return (o.length == 1 ? '0' : '') + o; + } - var CPAL = font["CPAL"], gl = COLR[0]["g" + gid]; + var CPAL = font['CPAL'], + gl = COLR[0]['g' + gid]; for (var i = 0; i < gl[1]; i++) { var lid = gl[0] + i; - var cgl = COLR[1][2 * lid], pid = COLR[1][2 * lid + 1] * 4; + var cgl = COLR[1][2 * lid], + pid = COLR[1][2 * lid + 1] * 4; var pth = glyphToPath(font, cgl, cgl == gid); - var col = "#" + toHex(CPAL[pid + 2]) + toHex(CPAL[pid + 1]) + toHex(CPAL[pid + 0]); + var col = + '#' + + toHex(CPAL[pid + 2]) + + toHex(CPAL[pid + 1]) + + toHex(CPAL[pid + 0]); path.cmds.push(col); - path.cmds = path.cmds.concat(pth["cmds"]); - path.crds = path.crds.concat(pth["crds"]); + path.cmds = path.cmds.concat(pth['cmds']); + path.crds = path.crds.concat(pth['crds']); //console.log(gid, cgl,pid,col); - path.cmds.push("X"); - } - } - else if (CFF) { - var pdct = CFF["Private"]; - var state = { x: 0, y: 0, stack: [], nStems: 0, haveWidth: false, width: pdct ? pdct["defaultWidthX"] : 0, open: false }; - if (CFF["ROS"]) { + path.cmds.push('X'); + } + } else if (CFF) { + var pdct = CFF['Private']; + var state = { + x: 0, + y: 0, + stack: [], + nStems: 0, + haveWidth: false, + width: pdct ? pdct['defaultWidthX'] : 0, + open: false + }; + if (CFF['ROS']) { var gi = 0; - while (CFF["FDSelect"][gi + 2] <= gid) gi += 2; - pdct = CFF["FDArray"][CFF["FDSelect"][gi + 1]]["Private"]; + while (CFF['FDSelect'][gi + 2] <= gid) gi += 2; + pdct = CFF['FDArray'][CFF['FDSelect'][gi + 1]]['Private']; } - _drawCFF(CFF["CharStrings"][gid], state, CFF, pdct, path); + _drawCFF(CFF['CharStrings'][gid], state, CFF, pdct, path); + } else if (font['glyf']) { + _drawGlyf(gid, font, path, axs); } - else if (font["glyf"]) { _drawGlyf(gid, font, path, axs); } - return { "cmds": path.cmds, "crds": path.crds }; + return { cmds: path.cmds, crds: path.crds }; } function _drawGlyf(gid, font, path, axs) { - var gl = font["glyf"][gid]; + var gl = font['glyf'][gid]; - if (gl == null) gl = font["glyf"][gid] = Typr["T"].glyf._parseGlyf(font, gid); + if (gl == null) + gl = font['glyf'][gid] = Typr['T'].glyf._parseGlyf(font, gid); if (gl != null) { if (gl.noc > -1) _simpleGlyph(gl, font, gid, path, axs); else _compoGlyph(gl, font, gid, path, axs); } } function _interpolate(axs, v) { - var acnt = v.length, S = 1; - var s = axs[0]; // start - var p = axs[1]; // peak - var e = axs[2]; // end + var acnt = v.length, + S = 1; + var s = axs[0]; // start + var p = axs[1]; // peak + var e = axs[2]; // end for (var i = 0; i < v.length; i++) { var AS = 1; @@ -2149,87 +2876,123 @@ Typr["U"] = function () { return S; } function _normalizeAxis(font, vv) { - var fvar = font["fvar"], avar = font["avar"]; + var fvar = font['fvar'], + avar = font['avar']; var fv = fvar ? fvar[0] : null; var nv = []; for (var i = 0; i < fv.length; i++) { - var min = fv[i][1], def = fv[i][2], max = fv[i][3], v = Math.max(min, Math.min(max, vv[i])); + var min = fv[i][1], + def = fv[i][2], + max = fv[i][3], + v = Math.max(min, Math.min(max, vv[i])); if (v < def) nv[i] = (def - v) / (min - def); else if (v > def) nv[i] = (v - def) / (max - def); else nv[i] = 0; if (avar && nv[i] != -1) { - var av = avar[i], j = 0; + var av = avar[i], + j = 0; for (; j < av.length; j += 2) if (av[j] >= nv[i]) break; var f = (nv[i] - av[j - 2]) / (av[j] - av[j - 2]); nv[i] = f * av[j + 1] + (1 - f) * av[j - 1]; } - } return nv; } function interpolateDeltas(dfs, ind, xs, ys, endPts) { - var N = xs.length, ndfs = new Array(N * 2 + 8); ndfs.fill(0); + var N = xs.length, + ndfs = new Array(N * 2 + 8); + ndfs.fill(0); for (var i = 0; i < N; i++) { - var dx = 0, dy = 0, ii = ind.indexOf(i); - if (ii != -1) { dx = dfs[ii]; dy = dfs[ind.length + ii]; } - else { - var cmp = 0; while (endPts[cmp] < i) cmp++; + var dx = 0, + dy = 0, + ii = ind.indexOf(i); + if (ii != -1) { + dx = dfs[ii]; + dy = dfs[ind.length + ii]; + } else { + var cmp = 0; + while (endPts[cmp] < i) cmp++; var cmp0 = cmp == 0 ? 0 : endPts[cmp - 1] + 1; var cmp1 = endPts[cmp]; - var i0 = -1, i1 = -1; + var i0 = -1, + i1 = -1; - for (var j = 0; j < ind.length; j++) { var v = ind[j]; if (v < cmp0 || v > cmp1 || v >= N) continue; i0 = j; if (i1 == -1) i1 = j; } - for (var j = 0; j < ind.length; j++) { var v = ind[j]; if (v < cmp0 || v > cmp1 || v >= N) continue; if (v < i) i0 = j; if (i < v) { i1 = j; break; } } + for (var j = 0; j < ind.length; j++) { + var v = ind[j]; + if (v < cmp0 || v > cmp1 || v >= N) continue; + i0 = j; + if (i1 == -1) i1 = j; + } + for (var j = 0; j < ind.length; j++) { + var v = ind[j]; + if (v < cmp0 || v > cmp1 || v >= N) continue; + if (v < i) i0 = j; + if (i < v) { + i1 = j; + break; + } + } //var i0 = ind.length-1, i1=0; if(ind[i0]>=N) i0--; //for(var j=0; j= 0) no.push(" "); - no.push(it); lstF = isF; - } - return no.join(""); - } - - - var out = [], co = 0, lmap = { "M": 2, "L": 2, "Q": 4, "C": 6 }; - var x = 0, y = 0, // perfect coords + var it = o[i], + isF = typeof it == 'number'; + if (!isF) { + if (it == lstC && it.length == 1 && it != 'm') continue; + lstC = it; + } // move should not be merged (it actually means lineTo) + if (lstF && isF && it >= 0) no.push(' '); + no.push(it); + lstF = isF; + } + return no.join(''); + } + + var out = [], + co = 0, + lmap = { M: 2, L: 2, Q: 4, C: 6 }; + var x = 0, + y = 0, // perfect coords //dx=0, dy=0, // relative perfect coords //rx=0, ry=0, // relative rounded coords - ex = 0, ey = 0, // error between perfect and output coords - mx = 0, my = 0; // perfect coords of the last "Move" + ex = 0, + ey = 0, // error between perfect and output coords + mx = 0, + my = 0; // perfect coords of the last "Move" for (var i = 0; i < cmds.length; i++) { - var cmd = cmds[i], cc = (lmap[cmd] ? lmap[cmd] : 0); - - var o0 = [], dx, dy, rx, ry; // o1=[], cx, cy, ax,ay; - if (cmd == "L") { - dx = crds[co] - x; dy = crds[co + 1] - y; - rx = num(dx + ex); ry = num(dy + ey); + var cmd = cmds[i], + cc = lmap[cmd] ? lmap[cmd] : 0; + + var o0 = [], + dx, + dy, + rx, + ry; // o1=[], cx, cy, ax,ay; + if (cmd == 'L') { + dx = crds[co] - x; + dy = crds[co + 1] - y; + rx = num(dx + ex); + ry = num(dy + ey); // if this "lineTo" leads to the starting point, and "Z" follows, do not output anything. - if (cmds[i + 1] == "Z" && crds[co] == mx && crds[co + 1] == my) { rx = dx; ry = dy; } - else if (rx == 0 && ry == 0) { } - else if (rx == 0) o0.push("v", ry); - else if (ry == 0) o0.push("h", rx); - else { o0.push("l", rx, ry); } - } - else { + if (cmds[i + 1] == 'Z' && crds[co] == mx && crds[co + 1] == my) { + rx = dx; + ry = dy; + } else if (rx == 0 && ry == 0) { + } else if (rx == 0) o0.push('v', ry); + else if (ry == 0) o0.push('h', rx); + else { + o0.push('l', rx, ry); + } + } else { o0.push(cmd.toLowerCase()); for (var j = 0; j < cc; j += 2) { - dx = crds[co + j] - x; dy = crds[co + j + 1] - y; - rx = num(dx + ex); ry = num(dy + ey); + dx = crds[co + j] - x; + dy = crds[co + j + 1] - y; + rx = num(dx + ex); + ry = num(dy + ey); o0.push(rx, ry); } } - if (cc != 0) { ex += dx - rx; ey += dy - ry; } + if (cc != 0) { + ex += dx - rx; + ey += dy - ry; + } var ou = o0; for (var j = 0; j < ou.length; j++) out.push(ou[j]); - if (cc != 0) { co += cc; x = crds[co - 2]; y = crds[co - 1]; } - if (cmd == "M") { mx = x; my = y; } - if (cmd == "Z") { x = mx; y = my; } + if (cc != 0) { + co += cc; + x = crds[co - 2]; + y = crds[co - 1]; + } + if (cmd == 'M') { + mx = x; + my = y; + } + if (cmd == 'Z') { + x = mx; + y = my; + } } return merge(out); } function SVGToPath(d) { var pth = { cmds: [], crds: [] }; - Typr["U"]["SVG"].svgToPath(d, pth); - return { "cmds": pth.cmds, "crds": pth.crds }; + Typr['U']['SVG'].svgToPath(d, pth); + return { cmds: pth.cmds, crds: pth.crds }; } function mipmapB(buff, w, h, hlp) { - var nw = w >> 1, nh = h >> 1; - var nbuf = (hlp && hlp.length == nw * nh * 4) ? hlp : new Uint8Array(nw * nh * 4); - var sb32 = new Uint32Array(buff.buffer), nb32 = new Uint32Array(nbuf.buffer); + var nw = w >> 1, + nh = h >> 1; + var nbuf = + hlp && hlp.length == nw * nh * 4 ? hlp : new Uint8Array(nw * nh * 4); + var sb32 = new Uint32Array(buff.buffer), + nb32 = new Uint32Array(nbuf.buffer); for (var y = 0; y < nh; y++) for (var x = 0; x < nw; x++) { - var ti = (y * nw + x), si = ((y << 1) * w + (x << 1)); + var ti = y * nw + x, + si = (y << 1) * w + (x << 1); //nbuf[ti ] = buff[si ]; nbuf[ti+1] = buff[si+1]; nbuf[ti+2] = buff[si+2]; nbuf[ti+3] = buff[si+3]; //* - var c0 = sb32[si], c1 = sb32[si + 1], c2 = sb32[si + w], c3 = sb32[si + w + 1]; + var c0 = sb32[si], + c1 = sb32[si + 1], + c2 = sb32[si + w], + c3 = sb32[si + w + 1]; - var a0 = (c0 >>> 24), a1 = (c1 >>> 24), a2 = (c2 >>> 24), a3 = (c3 >>> 24), a = (a0 + a1 + a2 + a3); + var a0 = c0 >>> 24, + a1 = c1 >>> 24, + a2 = c2 >>> 24, + a3 = c3 >>> 24, + a = a0 + a1 + a2 + a3; if (a == 1020) { - var r = (((c0 >>> 0) & 255) + ((c1 >>> 0) & 255) + ((c2 >>> 0) & 255) + ((c3 >>> 0) & 255) + 2) >>> 2; - var g = (((c0 >>> 8) & 255) + ((c1 >>> 8) & 255) + ((c2 >>> 8) & 255) + ((c3 >>> 8) & 255) + 2) >>> 2; - var b = (((c0 >>> 16) & 255) + ((c1 >>> 16) & 255) + ((c2 >>> 16) & 255) + ((c3 >>> 16) & 255) + 2) >>> 2; + var r = + (((c0 >>> 0) & 255) + + ((c1 >>> 0) & 255) + + ((c2 >>> 0) & 255) + + ((c3 >>> 0) & 255) + + 2) >>> + 2; + var g = + (((c0 >>> 8) & 255) + + ((c1 >>> 8) & 255) + + ((c2 >>> 8) & 255) + + ((c3 >>> 8) & 255) + + 2) >>> + 2; + var b = + (((c0 >>> 16) & 255) + + ((c1 >>> 16) & 255) + + ((c2 >>> 16) & 255) + + ((c3 >>> 16) & 255) + + 2) >>> + 2; nb32[ti] = (255 << 24) | (b << 16) | (g << 8) | r; - } - else if (a == 0) nb32[ti] = 0; + } else if (a == 0) nb32[ti] = 0; else { - var r = ((c0 >>> 0) & 255) * a0 + ((c1 >>> 0) & 255) * a1 + ((c2 >>> 0) & 255) * a2 + ((c3 >>> 0) & 255) * a3; - var g = ((c0 >>> 8) & 255) * a0 + ((c1 >>> 8) & 255) * a1 + ((c2 >>> 8) & 255) * a2 + ((c3 >>> 8) & 255) * a3; - var b = ((c0 >>> 16) & 255) * a0 + ((c1 >>> 16) & 255) * a1 + ((c2 >>> 16) & 255) * a2 + ((c3 >>> 16) & 255) * a3; - - var ia = 1 / a; r = ~~(r * ia + 0.5); g = ~~(g * ia + 0.5); b = ~~(b * ia + 0.5); + var r = + ((c0 >>> 0) & 255) * a0 + + ((c1 >>> 0) & 255) * a1 + + ((c2 >>> 0) & 255) * a2 + + ((c3 >>> 0) & 255) * a3; + var g = + ((c0 >>> 8) & 255) * a0 + + ((c1 >>> 8) & 255) * a1 + + ((c2 >>> 8) & 255) * a2 + + ((c3 >>> 8) & 255) * a3; + var b = + ((c0 >>> 16) & 255) * a0 + + ((c1 >>> 16) & 255) * a1 + + ((c2 >>> 16) & 255) * a2 + + ((c3 >>> 16) & 255) * a3; + + var ia = 1 / a; + r = ~~(r * ia + 0.5); + g = ~~(g * ia + 0.5); + b = ~~(b * ia + 0.5); nb32[ti] = (((a + 2) >>> 2) << 24) | (b << 16) | (g << 8) | r; } } @@ -2410,97 +3275,143 @@ Typr["U"] = function () { var __cnv, __ct; function pathToContext(path, ctx) { - var c = 0, cmds = path["cmds"], crds = path["crds"]; + var c = 0, + cmds = path['cmds'], + crds = path['crds']; //ctx.translate(3500,500); ctx.rotate(0.25); ctx.scale(1,-1); for (var j = 0; j < cmds.length; j++) { var cmd = cmds[j]; - if (cmd == "M") { + if (cmd == 'M') { ctx.moveTo(crds[c], crds[c + 1]); c += 2; - } - else if (cmd == "L") { + } else if (cmd == 'L') { ctx.lineTo(crds[c], crds[c + 1]); c += 2; - } - else if (cmd == "C") { - ctx.bezierCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3], crds[c + 4], crds[c + 5]); + } else if (cmd == 'C') { + ctx.bezierCurveTo( + crds[c], + crds[c + 1], + crds[c + 2], + crds[c + 3], + crds[c + 4], + crds[c + 5] + ); c += 6; - } - else if (cmd == "Q") { + } else if (cmd == 'Q') { ctx.quadraticCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3]); c += 4; - } - else if (cmd[0] == "d") { - var upng = window["UPNG"]; - var x0 = crds[c], y0 = crds[c + 1], x1 = crds[c + 2], y1 = crds[c + 3], x2 = crds[c + 4], y2 = crds[c + 5], x3 = crds[c + 6], y3 = crds[c + 7]; c += 8; + } else if (cmd[0] == 'd') { + var upng = window['UPNG']; + var x0 = crds[c], + y0 = crds[c + 1], + x1 = crds[c + 2], + y1 = crds[c + 3], + x2 = crds[c + 4], + y2 = crds[c + 5], + x3 = crds[c + 6], + y3 = crds[c + 7]; + c += 8; //y0+=400; y1+=400; y1+=600; if (upng == null) { - ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.lineTo(x2, y2); ctx.lineTo(x3, y3); ctx.closePath(); + ctx.moveTo(x0, y0); + ctx.lineTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.lineTo(x3, y3); + ctx.closePath(); continue; } - var dx0 = (x1 - x0), dy0 = (y1 - y0), dx1 = (x3 - x0), dy1 = (y3 - y0); + var dx0 = x1 - x0, + dy0 = y1 - y0, + dx1 = x3 - x0, + dy1 = y3 - y0; var sbmp = atob(cmd.slice(22)); var bmp = new Uint8Array(sbmp.length); for (var i = 0; i < sbmp.length; i++) bmp[i] = sbmp.charCodeAt(i); - var img = upng["decode"](bmp.buffer), w = img["width"], h = img["height"]; //console.log(img); + var img = upng['decode'](bmp.buffer), + w = img['width'], + h = img['height']; //console.log(img); - var nbmp = new Uint8Array(upng["toRGBA8"](img)[0]); - var tr = ctx["getTransform"](); - var scl = Math.sqrt(Math.abs(tr["a"] * tr["d"] - tr["b"] * tr["c"])) * Math.sqrt(dx1 * dx1 + dy1 * dy1) / h; + var nbmp = new Uint8Array(upng['toRGBA8'](img)[0]); + var tr = ctx['getTransform'](); + var scl = + (Math.sqrt(Math.abs(tr['a'] * tr['d'] - tr['b'] * tr['c'])) * + Math.sqrt(dx1 * dx1 + dy1 * dy1)) / + h; while (scl < 0.5) { var nd = mipmapB(nbmp, w, h); - nbmp = nd.buff; w = nd.w; h = nd.h; scl *= 2; + nbmp = nd.buff; + w = nd.w; + h = nd.h; + scl *= 2; } - if (__cnv == null) { __cnv = document.createElement("canvas"); __ct = __cnv.getContext("2d"); } - if (__cnv.width != w || __cnv.height != h) { __cnv.width = w; __cnv.height = h; } + if (__cnv == null) { + __cnv = document.createElement('canvas'); + __ct = __cnv.getContext('2d'); + } + if (__cnv.width != w || __cnv.height != h) { + __cnv.width = w; + __cnv.height = h; + } - __ct.putImageData(new ImageData(new Uint8ClampedArray(nbmp.buffer), w, h), 0, 0); + __ct.putImageData( + new ImageData(new Uint8ClampedArray(nbmp.buffer), w, h), + 0, + 0 + ); ctx.save(); ctx.transform(dx0, dy0, dx1, dy1, x0, y0); ctx.scale(1 / w, 1 / h); ctx.drawImage(__cnv, 0, 0); //*/ ctx.restore(); - } - else if (cmd.charAt(0) == "#" || cmd.charAt(0) == "r") { + } else if (cmd.charAt(0) == '#' || cmd.charAt(0) == 'r') { ctx.beginPath(); ctx.fillStyle = cmd; - } - else if (cmd.charAt(0) == "O" && cmd != "OX") { + } else if (cmd.charAt(0) == 'O' && cmd != 'OX') { ctx.beginPath(); - var pts = cmd.split("-"); + var pts = cmd.split('-'); ctx.lineWidth = parseFloat(pts[2]); - ctx.lineCap = ["butt", "round", "square"][parseFloat(pts[3])]; - ctx.lineJoin = ["miter", "round", "bevel"][parseFloat(pts[4])]; + ctx.lineCap = ['butt', 'round', 'square'][parseFloat(pts[3])]; + ctx.lineJoin = ['miter', 'round', 'bevel'][parseFloat(pts[4])]; ctx.miterLimit = parseFloat(pts[5]); ctx.lineDashOffset = parseFloat(pts[6]); - ctx.setLineDash(pts[7].split(",").map(parseFloat)); + ctx.setLineDash(pts[7].split(',').map(parseFloat)); ctx.strokeStyle = pts[1]; - } - else if (cmd == "Z") { + } else if (cmd == 'Z') { ctx.closePath(); - } - else if (cmd == "X") { + } else if (cmd == 'X') { ctx.fill(); - } - else if (cmd == "OX") { + } else if (cmd == 'OX') { ctx.stroke(); } } } - function _drawCFF(cmds, state, font, pdct, p) { var stack = state.stack; - var nStems = state.nStems, haveWidth = state.haveWidth, width = state.width, open = state.open; + var nStems = state.nStems, + haveWidth = state.haveWidth, + width = state.width, + open = state.open; var i = 0; - var x = state.x, y = state.y, c1x = 0, c1y = 0, c2x = 0, c2y = 0, c3x = 0, c3y = 0, c4x = 0, c4y = 0, jpx = 0, jpy = 0; - var CFF = Typr["T"].CFF; - - var nominalWidthX = pdct["nominalWidthX"]; + var x = state.x, + y = state.y, + c1x = 0, + c1y = 0, + c2x = 0, + c2y = 0, + c3x = 0, + c3y = 0, + c4x = 0, + c4y = 0, + jpx = 0, + jpy = 0; + var CFF = Typr['T'].CFF; + + var nominalWidthX = pdct['nominalWidthX']; var o = { val: 0, size: 0 }; //console.log(cmds); while (i < cmds.length) { @@ -2508,8 +3419,8 @@ Typr["U"] = function () { var v = o.val; i += o.size; - if (false) { } - else if (v == "o1" || v == "o18") // hstem || hstemhm + if (false) { + } else if (v == 'o1' || v == 'o18') // hstem || hstemhm { var hasWidthArg; @@ -2523,8 +3434,7 @@ Typr["U"] = function () { nStems += stack.length >> 1; stack.length = 0; haveWidth = true; - } - else if (v == "o3" || v == "o23") // vstem || vstemhm + } else if (v == 'o3' || v == 'o23') // vstem || vstemhm { var hasWidthArg; @@ -2538,8 +3448,7 @@ Typr["U"] = function () { nStems += stack.length >> 1; stack.length = 0; haveWidth = true; - } - else if (v == "o4") { + } else if (v == 'o4') { if (stack.length > 1 && !haveWidth) { width = stack.shift() + nominalWidthX; haveWidth = true; @@ -2547,29 +3456,28 @@ Typr["U"] = function () { if (open) P.ClosePath(p); y += stack.pop(); - P.MoveTo(p, x, y); open = true; - } - else if (v == "o5") { + P.MoveTo(p, x, y); + open = true; + } else if (v == 'o5') { while (stack.length > 0) { x += stack.shift(); y += stack.shift(); P.LineTo(p, x, y); } - } - else if (v == "o6" || v == "o7") // hlineto || vlineto + } else if (v == 'o6' || v == 'o7') // hlineto || vlineto { var count = stack.length; - var isX = (v == "o6"); + var isX = v == 'o6'; for (var j = 0; j < count; j++) { var sval = stack.shift(); - if (isX) x += sval; else y += sval; + if (isX) x += sval; + else y += sval; isX = !isX; P.LineTo(p, x, y); } - } - else if (v == "o8" || v == "o24") // rrcurveto || rcurveline + } else if (v == 'o8' || v == 'o24') // rrcurveto || rcurveline { var count = stack.length; var index = 0; @@ -2583,74 +3491,77 @@ Typr["U"] = function () { P.CurveTo(p, c1x, c1y, c2x, c2y, x, y); index += 6; } - if (v == "o24") { + if (v == 'o24') { x += stack.shift(); y += stack.shift(); P.LineTo(p, x, y); } - } - else if (v == "o11") break; - else if (v == "o1234" || v == "o1235" || v == "o1236" || v == "o1237")//if((v+"").slice(0,3)=="o12") + } else if (v == 'o11') break; + else if ( + v == 'o1234' || + v == 'o1235' || + v == 'o1236' || + v == 'o1237' + ) //if((v+"").slice(0,3)=="o12") { - if (v == "o1234") { - c1x = x + stack.shift(); // dx1 - c1y = y; // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y; // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = c2y; // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = y; // dy5 - x = c4x + stack.shift(); // dx6 + if (v == 'o1234') { + c1x = x + stack.shift(); // dx1 + c1y = y; // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = y; // dy5 + x = c4x + stack.shift(); // dx6 P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); P.CurveTo(p, c3x, c3y, c4x, c4y, x, y); - } - if (v == "o1235") { - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y + stack.shift(); // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = jpy + stack.shift(); // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - x = c4x + stack.shift(); // dx6 - y = c4y + stack.shift(); // dy6 - stack.shift(); // flex depth + if (v == 'o1235') { + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + y = c4y + stack.shift(); // dy6 + stack.shift(); // flex depth P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); P.CurveTo(p, c3x, c3y, c4x, c4y, x, y); } - if (v == "o1236") { - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y; // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = c2y; // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 - x = c4x + stack.shift(); // dx6 + if (v == 'o1236') { + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); P.CurveTo(p, c3x, c3y, c4x, c4y, x, y); } - if (v == "o1237") { - c1x = x + stack.shift(); // dx1 - c1y = y + stack.shift(); // dy1 - c2x = c1x + stack.shift(); // dx2 - c2y = c1y + stack.shift(); // dy2 - jpx = c2x + stack.shift(); // dx3 - jpy = c2y + stack.shift(); // dy3 - c3x = jpx + stack.shift(); // dx4 - c3y = jpy + stack.shift(); // dy4 - c4x = c3x + stack.shift(); // dx5 - c4y = c3y + stack.shift(); // dy5 + if (v == 'o1237') { + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 if (Math.abs(c4x - x) > Math.abs(c4y - y)) { x = c4x + stack.shift(); } else { @@ -2659,22 +3570,19 @@ Typr["U"] = function () { P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); P.CurveTo(p, c3x, c3y, c4x, c4y, x, y); } - } - else if (v == "o14") { + } else if (v == 'o14') { if (stack.length > 0 && stack.length != 4 && !haveWidth) { - width = stack.shift() + font["nominalWidthX"]; + width = stack.shift() + font['nominalWidthX']; haveWidth = true; } if (stack.length == 4) // seac = standard encoding accented character { - var asb = 0; var adx = stack.shift(); var ady = stack.shift(); var bchar = stack.shift(); var achar = stack.shift(); - var bind = CFF.glyphBySE(font, bchar); var aind = CFF.glyphBySE(font, achar); @@ -2682,15 +3590,18 @@ Typr["U"] = function () { //console.log(achar, aind); //state.x=x; state.y=y; state.nStems=nStems; state.haveWidth=haveWidth; state.width=width; state.open=open; - _drawCFF(font["CharStrings"][bind], state, font, pdct, p); - state.x = adx; state.y = ady; - _drawCFF(font["CharStrings"][aind], state, font, pdct, p); + _drawCFF(font['CharStrings'][bind], state, font, pdct, p); + state.x = adx; + state.y = ady; + _drawCFF(font['CharStrings'][aind], state, font, pdct, p); //x=state.x; y=state.y; nStems=state.nStems; haveWidth=state.haveWidth; width=state.width; open=state.open; } - if (open) { P.ClosePath(p); open = false; } - } - else if (v == "o19" || v == "o20") { + if (open) { + P.ClosePath(p); + open = false; + } + } else if (v == 'o19' || v == 'o20') { var hasWidthArg; // The number of stem operators on the stack is always even. @@ -2705,9 +3616,7 @@ Typr["U"] = function () { haveWidth = true; i += (nStems + 7) >> 3; - } - - else if (v == "o21") { + } else if (v == 'o21') { if (stack.length > 2 && !haveWidth) { width = stack.shift() + nominalWidthX; haveWidth = true; @@ -2717,9 +3626,9 @@ Typr["U"] = function () { x += stack.pop(); if (open) P.ClosePath(p); - P.MoveTo(p, x, y); open = true; - } - else if (v == "o22") { + P.MoveTo(p, x, y); + open = true; + } else if (v == 'o22') { if (stack.length > 1 && !haveWidth) { width = stack.shift() + nominalWidthX; haveWidth = true; @@ -2728,9 +3637,9 @@ Typr["U"] = function () { x += stack.pop(); if (open) P.ClosePath(p); - P.MoveTo(p, x, y); open = true; - } - else if (v == "o25") { + P.MoveTo(p, x, y); + open = true; + } else if (v == 'o25') { while (stack.length > 6) { x += stack.shift(); y += stack.shift(); @@ -2744,8 +3653,7 @@ Typr["U"] = function () { x = c2x + stack.shift(); y = c2y + stack.shift(); P.CurveTo(p, c1x, c1y, c2x, c2y, x, y); - } - else if (v == "o26") { + } else if (v == 'o26') { if (stack.length % 2) { x += stack.shift(); } @@ -2759,9 +3667,7 @@ Typr["U"] = function () { y = c2y + stack.shift(); P.CurveTo(p, c1x, c1y, c2x, c2y, x, y); } - - } - else if (v == "o27") { + } else if (v == 'o27') { if (stack.length % 2) { y += stack.shift(); } @@ -2775,24 +3681,34 @@ Typr["U"] = function () { y = c2y; P.CurveTo(p, c1x, c1y, c2x, c2y, x, y); } - } - else if (v == "o10" || v == "o29") // callsubr || callgsubr + } else if (v == 'o10' || v == 'o29') // callsubr || callgsubr { - var obj = (v == "o10" ? pdct : font); - if (stack.length == 0) { console.log("error: empty stack"); } - else { + var obj = v == 'o10' ? pdct : font; + if (stack.length == 0) { + console.log('error: empty stack'); + } else { var ind = stack.pop(); - var subr = obj["Subrs"][ind + obj["Bias"]]; - state.x = x; state.y = y; state.nStems = nStems; state.haveWidth = haveWidth; state.width = width; state.open = open; + var subr = obj['Subrs'][ind + obj['Bias']]; + state.x = x; + state.y = y; + state.nStems = nStems; + state.haveWidth = haveWidth; + state.width = width; + state.open = open; _drawCFF(subr, state, font, pdct, p); - x = state.x; y = state.y; nStems = state.nStems; haveWidth = state.haveWidth; width = state.width; open = state.open; + x = state.x; + y = state.y; + nStems = state.nStems; + haveWidth = state.haveWidth; + width = state.width; + open = state.open; } - } - else if (v == "o30" || v == "o31") // vhcurveto || hvcurveto + } else if (v == 'o30' || v == 'o31') // vhcurveto || hvcurveto { - var count, count1 = stack.length; + var count, + count1 = stack.length; var index = 0; - var alternate = v == "o31"; + var alternate = v == 'o31'; count = count1 & ~2; index += count1 - count; @@ -2804,79 +3720,101 @@ Typr["U"] = function () { c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); y = c2y + stack.shift(); - if (count - index == 5) { x = c2x + stack.shift(); index++; } - else x = c2x; + if (count - index == 5) { + x = c2x + stack.shift(); + index++; + } else x = c2x; alternate = false; - } - else { + } else { c1x = x; c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); - if (count - index == 5) { y = c2y + stack.shift(); index++; } - else y = c2y; + if (count - index == 5) { + y = c2y + stack.shift(); + index++; + } else y = c2y; alternate = true; } P.CurveTo(p, c1x, c1y, c2x, c2y, x, y); index += 4; } - } - - else if ((v + "").charAt(0) == "o") { console.log("Unknown operation: " + v, cmds); throw v; } - else stack.push(v); + } else if ((v + '').charAt(0) == 'o') { + console.log('Unknown operation: ' + v, cmds); + throw v; + } else stack.push(v); } //console.log(cmds); - state.x = x; state.y = y; state.nStems = nStems; state.haveWidth = haveWidth; state.width = width; state.open = open; + state.x = x; + state.y = y; + state.nStems = nStems; + state.haveWidth = haveWidth; + state.width = width; + state.open = open; } function initHB(hurl, resp) { var codeLength = function (code) { var len = 0; - if ((code & (0xffffffff - (1 << 7) + 1)) == 0) { len = 1; } - else if ((code & (0xffffffff - (1 << 11) + 1)) == 0) { len = 2; } - else if ((code & (0xffffffff - (1 << 16) + 1)) == 0) { len = 3; } - else if ((code & (0xffffffff - (1 << 21) + 1)) == 0) { len = 4; } + if ((code & (0xffffffff - (1 << 7) + 1)) == 0) { + len = 1; + } else if ((code & (0xffffffff - (1 << 11) + 1)) == 0) { + len = 2; + } else if ((code & (0xffffffff - (1 << 16) + 1)) == 0) { + len = 3; + } else if ((code & (0xffffffff - (1 << 21) + 1)) == 0) { + len = 4; + } return len; - } + }; fetch(hurl) - .then(function (x) { return x["arrayBuffer"](); }) - .then(function (ab) { return WebAssembly["instantiate"](ab); }) + .then(function (x) { + return x['arrayBuffer'](); + }) + .then(function (ab) { + return WebAssembly['instantiate'](ab); + }) .then(function (res) { - console.log("HB ready"); - var exp = res["instance"]["exports"], mem = exp["memory"]; + console.log('HB ready'); + var exp = res['instance']['exports'], + mem = exp['memory']; //mem["grow"](30); // each page is 64kb in size var heapu8, u32, i32, f32; var __lastFnt, blob, blobPtr, face, font; - Typr["U"]["shapeHB"] = (function () { - + Typr['U']['shapeHB'] = (function () { var toJson = function (ptr) { - var length = exp["hb_buffer_get_length"](ptr); + var length = exp['hb_buffer_get_length'](ptr); var result = []; - var iPtr32 = exp["hb_buffer_get_glyph_infos"](ptr, 0) >>> 2; - var pPtr32 = exp["hb_buffer_get_glyph_positions"](ptr, 0) >>> 2; + var iPtr32 = exp['hb_buffer_get_glyph_infos'](ptr, 0) >>> 2; + var pPtr32 = exp['hb_buffer_get_glyph_positions'](ptr, 0) >>> 2; for (var i = 0; i < length; ++i) { - var a = iPtr32 + i * 5, b = pPtr32 + i * 5; + var a = iPtr32 + i * 5, + b = pPtr32 + i * 5; result.push({ - "g": u32[a + 0], - "cl": u32[a + 2], - "ax": i32[b + 0], - "ay": i32[b + 1], - "dx": i32[b + 2], - "dy": i32[b + 3] + g: u32[a + 0], + cl: u32[a + 2], + ax: i32[b + 0], + ay: i32[b + 1], + dx: i32[b + 2], + dy: i32[b + 3] }); } //console.log(result); return result; - } + }; var te; return function (fnt, str, prm) { - var fdata = fnt["_data"], fn = fnt["name"]["postScriptName"]; - var ltr = prm["ltr"], fts = prm["fts"], axs = prm["axs"]; - if (fnt["fvar"] && axs == null) axs = fnt["fvar"][1][fnt["_index"]][2]; + var fdata = fnt['_data'], + fn = fnt['name']['postScriptName']; + var ltr = prm['ltr'], + fts = prm['fts'], + axs = prm['axs']; + if (fnt['fvar'] && axs == null) + axs = fnt['fvar'][1][fnt['_index']][2]; //var olen = mem.buffer.byteLength, nlen = 2*fdata.length+str.length*16 + 4e6; //if(olen>>16)+4); //console.log("growing",nlen); @@ -2888,35 +3826,42 @@ Typr["U"] = function () { if (__lastFnt != fn) { if (blob != null) { - exp["hb_blob_destroy"](blob); - exp["free"](blobPtr); - exp["hb_face_destroy"](face); - exp["hb_font_destroy"](font); + exp['hb_blob_destroy'](blob); + exp['free'](blobPtr); + exp['hb_face_destroy'](face); + exp['hb_font_destroy'](font); } - blobPtr = exp["malloc"](fdata.byteLength); heapu8.set(fdata, blobPtr); - blob = exp["hb_blob_create"](blobPtr, fdata.byteLength, 2, 0, 0); - face = exp["hb_face_create"](blob, fnt["_index"]); - font = exp["hb_font_create"](face) + blobPtr = exp['malloc'](fdata.byteLength); + heapu8.set(fdata, blobPtr); + blob = exp['hb_blob_create'](blobPtr, fdata.byteLength, 2, 0, 0); + face = exp['hb_face_create'](blob, fnt['_index']); + font = exp['hb_font_create'](face); __lastFnt = fn; } - if (window["TextEncoder"] == null) { alert("Your browser is too old. Please, update it."); return; } - if (te == null) te = new window["TextEncoder"]("utf8"); + if (window['TextEncoder'] == null) { + alert('Your browser is too old. Please, update it.'); + return; + } + if (te == null) te = new window['TextEncoder']('utf8'); - var buffer = exp["hb_buffer_create"](); - var bytes = te["encode"](str); - var len = bytes.length, strp = exp["malloc"](len); heapu8.set(bytes, strp); - exp["hb_buffer_add_utf8"](buffer, strp, len, 0, len); - exp["free"](strp); + var buffer = exp['hb_buffer_create'](); + var bytes = te['encode'](str); + var len = bytes.length, + strp = exp['malloc'](len); + heapu8.set(bytes, strp); + exp['hb_buffer_add_utf8'](buffer, strp, len, 0, len); + exp['free'](strp); - var bin = Typr["B"]; + var bin = Typr['B']; var feat = 0; if (fts) { - feat = exp["malloc"](16 * fts.length); + feat = exp['malloc'](16 * fts.length); for (var i = 0; i < fts.length; i++) { var fe = fts[i]; - var off = feat + i * 16, qo = off >>> 2; - bin.writeASCII(heapu8, off, fe[0].split("").reverse().join("")); + var off = feat + i * 16, + qo = off >>> 2; + bin.writeASCII(heapu8, off, fe[0].split('').reverse().join('')); u32[qo + 1] = fe[1]; u32[qo + 2] = fe[2]; u32[qo + 3] = fe[3]; @@ -2924,47 +3869,66 @@ Typr["U"] = function () { //console.log(fts); } var vdat = 0; - if (axs && fnt["fvar"]) { - var axes = fnt["fvar"][0]; //console.log(axes, axs); - vdat = exp["malloc"](8 * axs.length); + if (axs && fnt['fvar']) { + var axes = fnt['fvar'][0]; //console.log(axes, axs); + vdat = exp['malloc'](8 * axs.length); for (var i = 0; i < axs.length; i++) { - var off = vdat + i * 8, qo = off >>> 2; - bin.writeASCII(heapu8, off, axes[i][0].split("").reverse().join("")); + var off = vdat + i * 8, + qo = off >>> 2; + bin.writeASCII( + heapu8, + off, + axes[i][0].split('').reverse().join('') + ); f32[qo + 1] = axs[i]; } } //*/ - if (axs) exp["hb_font_set_variations"](font, vdat, axs.length); - exp["hb_buffer_set_direction"](buffer, ltr ? 4 : 5); - exp["hb_buffer_guess_segment_properties"](buffer); - exp["hb_shape"](font, buffer, feat, fts ? fts.length : 0); - var json = toJson(buffer)//buffer["json"](); - exp["hb_buffer_destroy"](buffer); - if (fts) exp["free"](feat); - if (axs) exp["free"](vdat); - - var arr = json.slice(0); if (!ltr) arr.reverse(); - var ci = 0, bi = 0; // character index, binary index + if (axs) exp['hb_font_set_variations'](font, vdat, axs.length); + exp['hb_buffer_set_direction'](buffer, ltr ? 4 : 5); + exp['hb_buffer_guess_segment_properties'](buffer); + exp['hb_shape'](font, buffer, feat, fts ? fts.length : 0); + var json = toJson(buffer); //buffer["json"](); + exp['hb_buffer_destroy'](buffer); + if (fts) exp['free'](feat); + if (axs) exp['free'](vdat); + + var arr = json.slice(0); + if (!ltr) arr.reverse(); + var ci = 0, + bi = 0; // character index, binary index for (var i = 1; i < arr.length; i++) { - var gl = arr[i], cl = gl["cl"]; + var gl = arr[i], + cl = gl['cl']; while (true) { - var cpt = str.codePointAt(ci), cln = codeLength(cpt); - if (bi + cln <= cl) { bi += cln; ci += cpt <= 0xffff ? 1 : 2; } - else break; + var cpt = str.codePointAt(ci), + cln = codeLength(cpt); + if (bi + cln <= cl) { + bi += cln; + ci += cpt <= 0xffff ? 1 : 2; + } else break; } //while(bi+codeLength(str.charCodeAt(ci)) <=cl) { bi+=codeLength(str.charCodeAt(ci)); ci++; } - gl["cl"] = ci; + gl['cl'] = ci; } return json; - } - }()); + }; + })(); resp(); }); } - return { "shape": shape, "shapeToPath": shapeToPath, "codeToGlyph": codeToGlyph, "glyphToPath": glyphToPath, "pathToSVG": pathToSVG, "SVGToPath": SVGToPath, "pathToContext": pathToContext, "initHB": initHB }; -}(); - + return { + shape: shape, + shapeToPath: shapeToPath, + codeToGlyph: codeToGlyph, + glyphToPath: glyphToPath, + pathToSVG: pathToSVG, + SVGToPath: SVGToPath, + pathToContext: pathToContext, + initHB: initHB + }; +})(); export default Typr; diff --git a/src/type/p5.Font.js b/src/type/p5.Font.js index 1fbde9a79c..b12f34eb4d 100644 --- a/src/type/p5.Font.js +++ b/src/type/p5.Font.js @@ -45,13 +45,15 @@ import Typr from './lib/Typr.js'; import { createFromCommands } from '@davepagurek/bezier-path'; const pathArgCounts = { M: 2, L: 2, C: 6, Q: 4 }; -const validFontTypes = ['ttf', 'otf', 'woff'];//, 'woff2']; +const validFontTypes = ['ttf', 'otf', 'woff']; //, 'woff2']; const validFontTypesRe = new RegExp(`\\.(${validFontTypes.join('|')})`, 'i'); -const extractFontNameRe = new RegExp(`([^/]+)(\\.(?:${validFontTypes.join('|')}))`, 'i'); +const extractFontNameRe = new RegExp( + `([^/]+)(\\.(?:${validFontTypes.join('|')}))`, + 'i' +); const invalidFontError = 'Sorry, only TTF, OTF and WOFF files are supported.'; // and WOFF2 const fontFaceVariations = ['weight', 'stretch', 'style']; - export class Font { constructor(p, fontFace, name, path, data) { if (!(fontFace instanceof FontFace)) { @@ -191,12 +193,14 @@ export class Font { * } */ textToPaths(str, x, y, width, height, options) { - ({ width, height, options } = this._parseArgs(width, height, options)); if (!this.data) { - throw Error('No font data available for "' + this.name - + '"\nTry downloading a local copy of the font file'); + throw Error( + 'No font data available for "' + + this.name + + '"\nTry downloading a local copy of the font file' + ); } // lineate and get glyphs/paths for each line @@ -266,8 +270,10 @@ export class Font { // By segmenting per contour, pointAtLength becomes much faster const contourPoints = this.textToContours( str, - x, y, - width, height, + x, + y, + width, + height, options ); return contourPoints.reduce((acc, next) => { @@ -373,13 +379,13 @@ export class Font { * * The generated model (a Geometry object) can be manipulated further—rotated, scaled, * or styled with shaders—to create engaging, interactive visual art. - * - * The `options` parameter is also optional. `font.textToModel()` expects an object + * + * The `options` parameter is also optional. `font.textToModel()` expects an object * with the following properties: - * + * * `extrude` is the depth to extrude the text. It defaults to 0. A value of 0 produces * flat text; higher values create thicker, 3D models. - * + * * `sampleFactor` is a factor controlling the level of detail for the text contours. * It defaults to 1. Higher values result in smoother curves. * @@ -562,7 +568,8 @@ export class Font { // outer edges for extrusion. const vertexIndices = {}; - const vertexId = v => `${v.x.toFixed(6)}-${v.y.toFixed(6)}-${v.z.toFixed(6)}`; + const vertexId = v => + `${v.x.toFixed(6)}-${v.y.toFixed(6)}-${v.z.toFixed(6)}`; const newVertices = []; const newVertexIndex = []; @@ -704,7 +711,8 @@ export class Font { return meta; } - static async list(log = false) { // tmp + static async list(log = false) { + // tmp if (log) { console.log('There are', document.fonts.size, 'font-faces\n'); let loaded = 0; @@ -729,15 +737,20 @@ export class Font { Returns an array of line objects, each containing { text, x, y, glyphs: [ {g, path} ] } */ _lineateAndPathify(str, x, y, width, height, options = {}) { - let renderer = options?.graphics?._renderer || this._pInst._renderer; renderer.push(); renderer.textFont(this); // lineate and compute bounds for the text - let { lines, bounds } = renderer._computeBounds - (textCoreConstants._FONT_BOUNDS, str, x, y, width, height, - { ignoreRectMode: true, ...options }); + let { lines, bounds } = renderer._computeBounds( + textCoreConstants._FONT_BOUNDS, + str, + x, + y, + width, + height, + { ignoreRectMode: true, ...options } + ); // compute positions for each of the lines lines = this._position(renderer, lines, bounds, width, height); @@ -767,8 +780,9 @@ export class Font { // return renderer.states.fontStretch return 100; } else if (renderer.textCanvas().style.fontVariationSettings) { - const match = new RegExp(`\\b${tag}\s+(\d+)`) - .exec(renderer.textCanvas().style.fontVariationSettings); + const match = new RegExp(`\\b${tag}\s+(\d+)`).exec( + renderer.textCanvas().style.fontVariationSettings + ); if (match) { return parseInt(match[1]); } else { @@ -783,7 +797,6 @@ export class Font { } _textToPathPoints(str, x, y, width, height, options) { - ({ width, height, options } = this._parseArgs(width, height, options)); // lineate and get the points for each line @@ -819,12 +832,10 @@ export class Font { } _parseArgs(width, height, options = {}) { - if (typeof width === 'object') { options = width; width = height = undefined; - } - else if (typeof height === 'object') { + } else if (typeof height === 'object') { options = height; height = undefined; } @@ -832,20 +843,18 @@ export class Font { } _position(renderer, lines, bounds, width, height) { - let { textAlign, textLeading, textSize } = renderer.states; let metrics = this._measureTextDefault(renderer, 'X'); let ascent = metrics.fontBoundingBoxAscent; let coordify = (text, i) => { let x = bounds.x; - let y = bounds.y + (i * textLeading) + ascent; + let y = bounds.y + i * textLeading + ascent; let lineWidth = renderer._fontWidthSingle(text); if (textAlign === constants.CENTER) { x += (bounds.w - lineWidth) / 2; - } - else if (textAlign === constants.RIGHT) { - x += (bounds.w - lineWidth); + } else if (textAlign === constants.RIGHT) { + x += bounds.w - lineWidth; } if (typeof width !== 'undefined') { switch (renderer.states.rectMode) { @@ -866,10 +875,12 @@ export class Font { } _lineToGlyphs(line, { scale = 1, axs } = {}) { - if (!this.data) { - throw Error('No font data available for "' + this.name - + '"\nTry downloading a local copy of the font file'); + throw Error( + 'No font data available for "' + + this.name + + '"\nTry downloading a local copy of the font file' + ); } let glyphShapes = Typr.U.shape(this.data, line.text, { axs }); line.glyphShapes = glyphShapes; @@ -892,14 +903,10 @@ export class Font { return positionedGlyphs; } - _singleShapeToPath(shape, { - scale = 1, - x = 0, - y = 0, - lineX = 0, - lineY = 0, - axs - } = {}) { + _singleShapeToPath( + shape, + { scale = 1, x = 0, y = 0, lineX = 0, lineY = 0, axs } = {} + ) { let font = this.data; let crdIdx = 0; let { g, ax, ay, dx, dy } = shape; @@ -909,7 +916,8 @@ export class Font { let glyph = { /*g: line.text[i], points: [],*/ path: { commands: [] } }; for (let j = 0; j < cmds.length; j++) { - let type = cmds[j], command = [type]; + let type = cmds[j], + command = [type]; if (type in pathArgCounts) { let argCount = pathArgCounts[type]; for (let k = 0; k < argCount; k += 2) { @@ -932,7 +940,9 @@ export class Font { } _shapeToPaths(glyphs, line, { scale = 1, axs } = {}) { - let x = 0, y = 0, paths = []; + let x = 0, + y = 0, + paths = []; if (glyphs.length !== line.text.length) { throw Error('Invalid shape data'); @@ -951,7 +961,8 @@ export class Font { }); paths.push(glyph); - x += ax; y += ay; + x += ax; + y += ay; } return paths; @@ -968,7 +979,8 @@ export class Font { return metrics; } - drawPaths(ctx, commands, opts) { // for debugging + drawPaths(ctx, commands, opts) { + // for debugging ctx.strokeStyle = opts?.stroke || ctx.strokeStyle; ctx.fillStyle = opts?.fill || ctx.fillStyle; ctx.beginPath(); @@ -998,23 +1010,24 @@ export class Font { // iterate over the path, storing each non-control point for (let c = 0, j = 0; j < cmds.length; j++) { - let cmd = cmds[j], obj = { type: cmd, data: [] }; + let cmd = cmds[j], + obj = { type: cmd, data: [] }; if (cmd === 'M' || cmd === 'L') { obj.data.push(x + crds[c] * scale, y + crds[c + 1] * -scale); c += 2; - } - else if (cmd === 'C') { + } else if (cmd === 'C') { for (let i = 0; i < 6; i += 2) { obj.data.push( - x + crds[c + i] * scale, y + crds[c + i + 1] * -scale + x + crds[c + i] * scale, + y + crds[c + i + 1] * -scale ); } c += 6; - } - else if (cmd === 'Q') { + } else if (cmd === 'Q') { for (let i = 0; i < 4; i += 2) { obj.data.push( - x + crds[c + i] * scale, y + crds[c + i + 1] * -scale + x + crds[c + i] * scale, + y + crds[c + i + 1] * -scale ); } c += 4; @@ -1028,7 +1041,6 @@ export class Font { } async function create(pInst, name, path, descriptors, rawFont) { - let face = createFontFace(name, path, descriptors, rawFont); // load if we need to @@ -1044,7 +1056,6 @@ async function create(pInst, name, path, descriptors, rawFont) { return new Font(pInst, face, name, path, rawFont); } - function sanitizeFontName(name) { if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(name)) { name = "'" + String(name).replace(/'/g, "\\'") + "'"; @@ -1053,7 +1064,6 @@ function sanitizeFontName(name) { } function createFontFace(name, path, descriptors, rawFont) { - name = sanitizeFontName(name); let fontArg = rawFont?._compressedData ?? rawFont?._data; if (!fontArg) { @@ -1068,9 +1078,8 @@ function createFontFace(name, path, descriptors, rawFont) { if ((rawFont?.fvar?.length ?? 0) > 0) { descriptors = descriptors || {}; - for (const [ - tag, minVal, defaultVal, maxVal, flags, name - ] of rawFont.fvar[0]) { + for (const [tag, minVal, defaultVal, maxVal, flags, name] of rawFont + .fvar[0]) { if (tag === 'wght') { descriptors.weight = `${minVal} ${maxVal}`; } else if (tag === 'wdth') { @@ -1089,7 +1098,8 @@ function createFontFace(name, path, descriptors, rawFont) { } function extractFontName(font, path) { - let result, meta = font?.name; + let result, + meta = font?.name; // use the metadata if we have it if (meta) { @@ -1102,13 +1112,11 @@ function extractFontName(font, path) { } if (!result) { - // if not, try to extract the name from the path let matches = extractFontNameRe.exec(path); if (matches && matches.length >= 3) { result = matches[1]; - } - else { + } else { // give up and return the full path result = path; } @@ -1120,10 +1128,9 @@ function extractFontName(font, path) { } return result; -}; +} function pathToPoints(cmds, options, font) { - const parseOpts = (options, defaults) => { if (typeof options !== 'object') { options = defaults; @@ -1139,7 +1146,7 @@ function pathToPoints(cmds, options, font) { const at = (v, i) => { const s = v.length; - return v[i < 0 ? i % s + s : i % s]; + return v[i < 0 ? (i % s) + s : i % s]; }; const simplify = (pts, angle) => { @@ -1198,17 +1205,14 @@ function pathToPoints(cmds, options, font) { const mode = font._pInst.angleMode(); const DEGREES = font._pInst.DEGREES; for (let i = 0; i < totalPoints; i++) { - const length = path.getTotalLength() * ( - totalPoints === 1 - ? 0 - : (i / (totalPoints - 1)) - ); + const length = + path.getTotalLength() * (totalPoints === 1 ? 0 : i / (totalPoints - 1)); points.push({ ...path.getPointAtLength(length), get angle() { const angle = path.getAngleAtLength(length); if (mode === DEGREES) { - return angle * 180 / Math.PI; + return (angle * 180) / Math.PI; } else { return angle; } @@ -1229,14 +1233,16 @@ function pathToPoints(cmds, options, font) { function unquote(name) { // Unquote name from CSS - if ((name.startsWith('"') || name.startsWith("'")) && name.at(0) === name.at(-1)) { + if ( + (name.startsWith('"') || name.startsWith("'")) && + name.at(0) === name.at(-1) + ) { return name.slice(1, -1).replace(/\/(['"])/g, '$1'); } return name; } -function parseCreateArgs(...args/*path, name, onSuccess, onError*/) { - +function parseCreateArgs(...args /*path, name, onSuccess, onError*/) { // parse the path let path = args.shift(); if (typeof path !== 'string' || path.length === 0) { @@ -1259,8 +1265,7 @@ function parseCreateArgs(...args/*path, name, onSuccess, onError*/) { } else { error = arg; } - } - else if (typeof arg === 'object') { + } else if (typeof arg === 'object') { options = arg; } } @@ -1269,7 +1274,6 @@ function parseCreateArgs(...args/*path, name, onSuccess, onError*/) { } function font(p5, fn) { - /** * A class to describe fonts. Create through `loadFont()`. * @@ -1280,11 +1284,12 @@ function font(p5, fn) { /** * @private */ - fn.parseFontData = async function(pathOrData) { + fn.parseFontData = async function (pathOrData) { // load the raw font bytes - let result = pathOrData instanceof Uint8Array - ? pathOrData - : await fn.loadBytes(pathOrData); + let result = + pathOrData instanceof Uint8Array + ? pathOrData + : await fn.loadBytes(pathOrData); //console.log('result:', result); if (!result) { @@ -1447,7 +1452,9 @@ function font(p5, fn) { // for the likely case when we have to fetch the whole thing. info = await fetch(path); } - const isCSSFile = info.headers.get('content-type')?.startsWith('text/css'); + const isCSSFile = info.headers + .get('content-type') + ?.startsWith('text/css'); if (isCSSFile) { isCSS = true; path = await fetch(path).then(res => res.text()); @@ -1469,7 +1476,9 @@ function font(p5, fn) { const camelCaseKey = key .replace(/^font-/, '') .split('-') - .map((v, i) => i === 0 ? v : `${v[0].toUpperCase()}${v.slice(1)}`) + .map((v, i) => + i === 0 ? v : `${v[0].toUpperCase()}${v.slice(1)}` + ) .join(''); fontDescriptors[camelCaseKey] = style.getPropertyValue(key); } @@ -1498,15 +1507,18 @@ function font(p5, fn) { // TODO: handle multiple font faces? sets = sets || ['latin']; // Default to latin for now if omitted - const requestedGroups = (sets instanceof Array ? sets : [sets]) - .map(s => s.toLowerCase()); + const requestedGroups = (sets instanceof Array ? sets : [sets]).map(s => + s.toLowerCase() + ); // Grab thr named groups with names that include the requested keywords - const requestedCategories = unicodeRanges - .filter(r => requestedGroups.some( - g => r.category.includes(g) && + const requestedCategories = unicodeRanges.filter(r => + requestedGroups.some( + g => + r.category.includes(g) && // Only include extended character sets if specifically requested r.category.includes('ext') === g.includes('ext') - )); + ) + ); const requestedRanges = new Set( UnicodeRange.parse( requestedCategories.map(c => `U+${c.hexrange[0]}-${c.hexrange[1]}`) @@ -1518,13 +1530,11 @@ function font(p5, fn) { for (const font of possibleFonts) { if (!font.fontDescriptors.unicodeRange) continue; const fontRange = new Set( - UnicodeRange.parse( - font.fontDescriptors.unicodeRange.split(/,\s*/g) - ) + UnicodeRange.parse(font.fontDescriptors.unicodeRange.split(/,\s*/g)) ); - const rangeOverlap = [...fontRange.values()] - .filter(v => requestedRanges.has(v)) - .length; + const rangeOverlap = [...fontRange.values()].filter(v => + requestedRanges.has(v) + ).length; const targetDescriptors = { // Default to normal style at regular weight @@ -1533,23 +1543,21 @@ function font(p5, fn) { // Override from anything else passed in ...descriptors }; - const descriptorOverlap = Object.keys(font.fontDescriptors) - .filter(k => font.fontDescriptors[k] === targetDescriptors[k]) - .length; + const descriptorOverlap = Object.keys(font.fontDescriptors).filter( + k => font.fontDescriptors[k] === targetDescriptors[k] + ).length; if ( descriptorOverlap > closestDescriptorOverlap || - ( - descriptorOverlap === closestDescriptorOverlap && - rangeOverlap >= closestRangeOverlap - ) + (descriptorOverlap === closestDescriptorOverlap && + rangeOverlap >= closestRangeOverlap) ) { closestDescriptorOverlap = descriptorOverlap; closestRangeOverlap = rangeOverlap; closestMatch = font; } } - const picked = (closestMatch || possibleFonts.at(-1)); + const picked = closestMatch || possibleFonts.at(-1); for (const font of possibleFonts) { if (font !== picked) { // Load without parsing data with Typr so that it still can be accessed @@ -1569,20 +1577,18 @@ function font(p5, fn) { // create a FontFace object and pass it to the p5.Font constructor pfont = await create(this, name, path, descriptors, fontData); - } catch (err) { // failed to parse the font, load it as a simple FontFace - let ident = name || path - .substring(path.lastIndexOf('/') + 1) - .replace(/\.[^/.]+$/, ''); + let ident = + name || + path.substring(path.lastIndexOf('/') + 1).replace(/\.[^/.]+$/, ''); console.warn(`WARN: No glyph data for '${ident}', retrying as FontFace`); try { // create a FontFace object and pass it to p5.Font pfont = await create(this, ident, path, descriptors); - } - catch (err) { + } catch (err) { if (error) return error(err); throw err; } @@ -1593,33 +1599,34 @@ function font(p5, fn) { }; return this._internal ? this._internal(cb) : cb(); }; -}; +} // Convert arrays to named objects -export const arrayCommandsToObjects = commands => commands.map(command => { - const type = command[0]; - switch (type) { - case 'Z': { - return { type }; - } - case 'M': - case 'L': { - const [, x, y] = command; - return { type, x, y }; - } - case 'Q': { - const [, x1, y1, x, y] = command; - return { type, x1, y1, x, y }; - } - case 'C': { - const [, x1, y1, x2, y2, x, y] = command; - return { type, x1, y1, x2, y2, x, y }; - } - default: { - throw new Error(`Unexpected path command: ${type}`); +export const arrayCommandsToObjects = commands => + commands.map(command => { + const type = command[0]; + switch (type) { + case 'Z': { + return { type }; + } + case 'M': + case 'L': { + const [, x, y] = command; + return { type, x, y }; + } + case 'Q': { + const [, x1, y1, x, y] = command; + return { type, x1, y1, x, y }; + } + case 'C': { + const [, x1, y1, x2, y2, x, y] = command; + return { type, x1, y1, x2, y2, x, y }; + } + default: { + throw new Error(`Unexpected path command: ${type}`); + } } - } -}); + }); export { sanitizeFontName as _sanitizeFontName }; export default font; diff --git a/src/type/textCore.js b/src/type/textCore.js index b978fdee12..72bb835e6f 100644 --- a/src/type/textCore.js +++ b/src/type/textCore.js @@ -661,7 +661,6 @@ function textCore(p5, fn) { * @returns {NORMAL|BOLD|ITALIC|BOLDITALIC} */ - /** * Calculates the width of the given text string in pixels. * @@ -1314,30 +1313,51 @@ function textCore(p5, fn) { textLeading: { default: 15 }, textSize: { default: 12 }, textWrap: { default: fn.WORD }, - fontStretch: { default: fn.NORMAL, isShorthand: true }, // font-stretch: { default: normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded } - fontWeight: { default: fn.NORMAL, isShorthand: true }, // font-stretch: { default: normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded } - lineHeight: { default: fn.NORMAL, isShorthand: true }, // line-height: { default: normal | number | length | percentage } - fontVariant: { default: fn.NORMAL, isShorthand: true }, // font-variant: { default: normal | small-caps } - fontStyle: { default: fn.NORMAL, isShorthand: true }, // font-style: { default: normal | italic | oblique } [was 'textStyle' in v1] + fontStretch: { default: fn.NORMAL, isShorthand: true }, // font-stretch: { default: normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded } + fontWeight: { default: fn.NORMAL, isShorthand: true }, // font-stretch: { default: normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded } + lineHeight: { default: fn.NORMAL, isShorthand: true }, // line-height: { default: normal | number | length | percentage } + fontVariant: { default: fn.NORMAL, isShorthand: true }, // font-variant: { default: normal | small-caps } + fontStyle: { default: fn.NORMAL, isShorthand: true }, // font-style: { default: normal | italic | oblique } [was 'textStyle' in v1] direction: { default: 'inherit' } // direction: { default: inherit | ltr | rtl } }; // note: font must be first here otherwise it may reset other properties - const ContextTextProps = ['font', 'direction', 'fontKerning', 'fontStretch', 'fontVariantCaps', 'letterSpacing', 'textAlign', 'textBaseline', 'textRendering', 'wordSpacing']; + const ContextTextProps = [ + 'font', + 'direction', + 'fontKerning', + 'fontStretch', + 'fontVariantCaps', + 'letterSpacing', + 'textAlign', + 'textBaseline', + 'textRendering', + 'wordSpacing' + ]; // shorthand font properties that can be set with context2d.font - const ShorthandFontProps = Object.keys(RendererTextProps) - .filter(p => RendererTextProps[p].isShorthand); + const ShorthandFontProps = Object.keys(RendererTextProps).filter( + p => RendererTextProps[p].isShorthand + ); // allowable values for font-stretch property for context2d.font - const FontStretchKeys = ['ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'normal', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded']; + const FontStretchKeys = [ + 'ultra-condensed', + 'extra-condensed', + 'condensed', + 'semi-condensed', + 'normal', + 'semi-expanded', + 'expanded', + 'extra-expanded', + 'ultra-expanded' + ]; let contextQueue, cachedDiv; // lazy ////////////////////////////// start API /////////////////////////////// Renderer.prototype.text = function (str, x, y, width, height) { - let setBaseline = this.textDrawingContext().textBaseline; // store baseline // adjust {x,y,w,h} properties based on rectMode @@ -1370,8 +1390,10 @@ function textCore(p5, fn) { return this._computeBounds( textCoreConstants._TEXT_BOUNDS, str, - x, y, - width, height + x, + y, + width, + height ).bounds; }; @@ -1390,8 +1412,10 @@ function textCore(p5, fn) { return this._computeBounds( textCoreConstants._FONT_BOUNDS, str, - x, y, - width, height + x, + y, + width, + height ).bounds; }; @@ -1453,12 +1477,11 @@ function textCore(p5, fn) { return this.textDrawingContext().measureText('_').fontBoundingBoxDescent; }; - // setters/getters for text properties ////////////////////////// Renderer.prototype.textAlign = function (h, v) { - - if (arguments.length === 0) { // the getter + if (arguments.length === 0) { + // the getter return { horizontal: this.states.textAlign, vertical: this.states.textBaseline @@ -1503,7 +1526,6 @@ function textCore(p5, fn) { * @private */ Renderer.prototype.textFont = function (font, size, options) { - if (arguments.length === 0) { return this._currentTextFont(); } @@ -1513,14 +1535,12 @@ function textCore(p5, fn) { // do we have a custon loaded font ? if (font instanceof p5.Font) { family = font.face.family; - } - else if (font.data instanceof Uint8Array) { + } else if (font.data instanceof Uint8Array) { family = font.name.fontFamily; if (font.name?.fontSubfamily) { family += '-' + font.name.fontSubfamily; } - } - else if (typeof font === 'string') { + } else if (typeof font === 'string') { // direct set the font-string if it contains size if (typeof size === 'undefined' && /[.0-9]+(%|em|p[xt])/.test(family)) { //console.log('direct set font-string: ', family); @@ -1565,7 +1585,8 @@ function textCore(p5, fn) { let style = getComputedStyle(el); ShorthandFontProps.forEach(prop => { this.states[prop] = style[prop]; - if (debug) console.log(' this.states.' + prop + '="' + style[prop] + '"'); + if (debug) + console.log(' this.states.' + prop + '="' + style[prop] + '"'); }); return { family: style.fontFamily, size: style.fontSize }; @@ -1598,7 +1619,6 @@ function textCore(p5, fn) { * @private */ Renderer.prototype.textSize = function (size) { - // the setter if (typeof size !== 'undefined') { this._setTextSize(size); @@ -1609,7 +1629,6 @@ function textCore(p5, fn) { }; Renderer.prototype.textStyle = function (style) { - // the setter if (typeof style !== 'undefined') { this.states.setValue('fontStyle', style); @@ -1620,7 +1639,6 @@ function textCore(p5, fn) { }; Renderer.prototype.textWrap = function (wrapStyle) { - if (wrapStyle === fn.WORD || wrapStyle === fn.CHAR) { this.states.setValue('textWrap', wrapStyle); // no need to apply text properties here as not a context property @@ -1630,7 +1648,6 @@ function textCore(p5, fn) { }; Renderer.prototype.textDirection = function (direction) { - if (typeof direction !== 'undefined') { this.states.setValue('direction', direction); return this._applyTextProperties(); @@ -1646,8 +1663,8 @@ function textCore(p5, fn) { * @private */ Renderer.prototype.textProperty = function (prop, value, opts) { - - let modified = false, debug = opts?.debug || false; + let modified = false, + debug = opts?.debug || false; // getter: return option from this.states or this.textDrawingContext() if (typeof value === 'undefined') { @@ -1673,8 +1690,7 @@ function textCore(p5, fn) { else if (prop in this.textCanvas().style) { this._setCanvasStyleProperty(prop, value, debug); modified = true; - } - else { + } else { console.warn('Ignoring unknown text option: "' + prop + '"\n'); // FES? } @@ -1687,7 +1703,6 @@ function textCore(p5, fn) { * @private */ Renderer.prototype.textProperties = function (properties) { - // setter if (typeof properties !== 'undefined') { Object.keys(properties).forEach(opt => { @@ -1707,8 +1722,8 @@ function textCore(p5, fn) { Object.keys(RendererTextProps).forEach(p => { if (RendererTextProps[p]?.type === 'Context2d') { properties[p] = context[p]; - } - else { // a renderer.states property + } else { + // a renderer.states property if (p === 'textFont') { // avoid circular ref. inside textFont let current = this._currentTextFont(); @@ -1717,8 +1732,7 @@ function textCore(p5, fn) { delete current._pInst; } properties[p] = current; - } - else { + } else { properties[p] = this.states[p]; } } @@ -1727,7 +1741,9 @@ function textCore(p5, fn) { return properties; }; - Renderer.prototype.textMode = function () { /* no-op for processing api */ }; + Renderer.prototype.textMode = function () { + /* no-op for processing api */ + }; /////////////////////////////// end API //////////////////////////////// @@ -1743,11 +1759,12 @@ function textCore(p5, fn) { Renderer.prototype._computeBounds = function ( type, str, - x, y, - width, height, + x, + y, + width, + height, opts ) { - let context = this.textDrawingContext(); let setBaseline = context.textBaseline; let { textLeading, textAlign } = this.states; @@ -1759,13 +1776,22 @@ function textCore(p5, fn) { let lines = this._processLines(str, width, height); // get the adjusted positions [x,y] for each line - let boxes = lines.map((line, i) => this[type].bind(this) - (line, x, y + i * textLeading)); + let boxes = lines.map((line, i) => + this[type].bind(this)(line, x, y + i * textLeading) + ); - if (lines.length > 1 && typeof width !== 'undefined') { // fix for #7984 + if (lines.length > 1 && typeof width !== 'undefined') { + // fix for #7984 // adjust the bounding boxes for horizontal text alignment in 2d // the WebGL mode version does additional alignment adjustments - boxes.forEach(bb => bb.x += p5.Renderer2D.prototype._xAlignOffset.call(this, textAlign, width)); + boxes.forEach( + bb => + (bb.x += p5.Renderer2D.prototype._xAlignOffset.call( + this, + textAlign, + width + )) + ); } // adjust the bounding boxes for vertical text alignment in 2d @@ -1775,7 +1801,6 @@ function textCore(p5, fn) { // get the bounds for the text block let bounds = boxes[0]; if (lines.length > 1) { - // get the bounds for the multi-line text block bounds = this._aggregateBounds(boxes); @@ -1785,7 +1810,8 @@ function textCore(p5, fn) { } } - if (0 && opts?.ignoreRectMode) { // draw bounds for debugging + if (0 && opts?.ignoreRectMode) { + // draw bounds for debugging let ss = context.strokeStyle; context.strokeStyle = 'green'; context.strokeRect(bounds.x, bounds.y, bounds.w, bounds.h); @@ -1802,7 +1828,6 @@ function textCore(p5, fn) { * @private */ Renderer.prototype._rectModeAdjust = function (x, y, width, height) { - if (typeof width !== 'undefined') { switch (this.states.rectMode) { case fn.CENTER: @@ -1825,7 +1850,6 @@ function textCore(p5, fn) { * @private */ Renderer.prototype._setCanvasStyleProperty = function (opt, val, debug) { - let value = val.toString(); // ensure its a string if (debug) console.log('canvas.style.' + opt + '="' + value + '"'); @@ -1840,12 +1864,17 @@ function textCore(p5, fn) { // check if the value was set successfully if (this.textCanvas().style[opt] !== value) { - // fails on precision for floating points, also quotes and spaces - if (0) console.warn(`Unable to set '${opt}' property` // FES? - + ' on canvas.style. It may not be supported. Expected "' - + value + '" but got: "' + this.textCanvas().style[opt] + "'"); + if (0) + console.warn( + `Unable to set '${opt}' property` + // FES? + ' on canvas.style. It may not be supported. Expected "' + + value + + '" but got: "' + + this.textCanvas().style[opt] + + "'" + ); } }; @@ -1855,11 +1884,14 @@ function textCore(p5, fn) { * @private */ Renderer.prototype._handleFontVariationSettings = function ( - value, debug = false + value, + debug = false ) { // check if the value is a string or an object if (typeof value === 'object') { - value = Object.keys(value).map(k => k + ' ' + value[k]).join(', '); + value = Object.keys(value) + .map(k => k + ' ' + value[k]) + .join(', '); } let values = value.split(CommaDelimRe); values.forEach(v => { @@ -1877,21 +1909,23 @@ function textCore(p5, fn) { if (this.states.fontWeight !== val) this.textWeight(val); return val; case 'wdth': - if (0) { // attempt to map font-stretch to allowed keywords + if (0) { + // attempt to map font-stretch to allowed keywords const FontStretchMap = { 'ultra-condensed': 50, 'extra-condensed': 62.5, - 'condensed': 75, + condensed: 75, 'semi-condensed': 87.5, - 'normal': 100, + normal: 100, 'semi-expanded': 112.5, - 'expanded': 125, + expanded: 125, 'extra-expanded': 150, 'ultra-expanded': 200 }; let values = Object.values(FontStretchMap); - const indexArr = values - .map(function (k) { return Math.abs(k - val); }); + const indexArr = values.map(function (k) { + return Math.abs(k - val); + }); const min = Math.min.apply(Math, indexArr); let idx = indexArr.indexOf(min); let stretch = Object.keys(FontStretchMap)[idx]; @@ -1899,10 +1933,12 @@ function textCore(p5, fn) { } break; case 'ital': - if (debug) console.log('setting font-style=' + (val ? 'italic' : 'normal')); + if (debug) + console.log('setting font-style=' + (val ? 'italic' : 'normal')); break; case 'slnt': - if (debug) console.log('setting font-style=' + (val ? 'oblique' : 'normal')); + if (debug) + console.log('setting font-style=' + (val ? 'oblique' : 'normal')); break; case 'opsz': if (debug) console.log('setting font-optical-size=' + val); @@ -1912,16 +1948,12 @@ function textCore(p5, fn) { }); }; - - - /* For properties not directly managed by the renderer in this.states we check if it has a mapping to a property in this.states Otherwise, add the property to the context-queue for later application */ Renderer.prototype._setContextProperty = function (prop, val, debug = false) { - // check if the value is actually different, else short-circuit if (this.textDrawingContext()[prop] === val) { return this._pInst; @@ -1938,7 +1970,6 @@ function textCore(p5, fn) { Adjust parameters (x,y,w,h) based on current rectMode */ Renderer.prototype._handleRectMode = function (x, y, width, height) { - let rectMode = this.states.rectMode; if (typeof width !== 'undefined') { @@ -2009,7 +2040,6 @@ function textCore(p5, fn) { return cachedDiv; }; - /* Aggregate the bounding boxes of multiple lines of text @param {Array} bboxes - the bounding boxes to aggregate @@ -2032,7 +2062,6 @@ function textCore(p5, fn) { // let w = Math.max(...bboxes.map(b => (b.x - tx) + b.w)); // let h = bboxes[bboxes.length - 1].y - bboxes[0].y + bboxes[bboxes.length - 1].h; - // return { x, y, w, h }; // }; @@ -2044,8 +2073,8 @@ function textCore(p5, fn) { * @private */ Renderer.prototype._processLines = function (str, width, height) { - - if (typeof width !== 'undefined') { // only for text with bounds + if (typeof width !== 'undefined') { + // only for text with bounds let drawingContext = this.textDrawingContext(); if (drawingContext.textBaseline === fn.BASELINE) { this.drawingContext.textBaseline = fn.TOP; @@ -2055,8 +2084,8 @@ function textCore(p5, fn) { let lines = this._splitOnBreaks(str.toString()); let hasLineBreaks = lines.length > 1; let hasWidth = typeof width !== 'undefined'; - let exceedsWidth = hasWidth && - lines.some(l => this._textWidthSingle(l) > width); + let exceedsWidth = + hasWidth && lines.some(l => this._textWidthSingle(l) > width); let { textLeading: leading, textWrap } = this.states; //if (!hasLineBreaks && !exceedsWidth) return lines; // a single-line @@ -2066,7 +2095,6 @@ function textCore(p5, fn) { // handle height truncation if (hasWidth && typeof height !== 'undefined') { - if (typeof leading === 'undefined') { throw Error('leading is required if height is specified'); } @@ -2110,7 +2138,6 @@ function textCore(p5, fn) { */ Renderer.prototype._rectModeAlign = function (bb, width, height) { if (typeof width !== 'undefined') { - switch (this.states.rectMode) { case fn.CENTER: bb.x -= (width - bb.w) / 2; @@ -2133,7 +2160,6 @@ function textCore(p5, fn) { Renderer.prototype._rectModeAlignRevert = function (bb, width, height) { if (typeof width !== 'undefined') { - switch (this.states.rectMode) { case fn.CENTER: bb.x += (width - bb.w) / 2; @@ -2175,7 +2201,6 @@ function textCore(p5, fn) { Get the (tight) bounds of a single line of text based on its actual bounding box */ Renderer.prototype._textBoundsSingle = function (s, x = 0, y = 0) { - let metrics = this.textDrawingContext().measureText(s); let asc = metrics.actualBoundingBoxAscent; let desc = metrics.actualBoundingBoxDescent; @@ -2188,12 +2213,11 @@ function textCore(p5, fn) { Get the (loose) bounds of a single line of text based on its font's bounding box */ Renderer.prototype._fontBoundsSingle = function (s, x = 0, y = 0) { - let metrics = this.textDrawingContext().measureText(s); let asc = metrics.fontBoundingBoxAscent; let desc = metrics.fontBoundingBoxDescent; x -= this._xAlignOffset(this.states.textAlign, metrics.width); - return { x, y: y - asc, w: metrics.width, h: asc + desc };; + return { x, y: y - asc, w: metrics.width, h: asc + desc }; }; /* @@ -2202,7 +2226,6 @@ function textCore(p5, fn) { @returns {boolean} - true if the size was changed, false otherwise */ Renderer.prototype._setTextSize = function (theSize) { - if (typeof theSize === 'string') { // parse the size string via computed style, eg '2em' theSize = this._fontSizePx(theSize); @@ -2210,19 +2233,20 @@ function textCore(p5, fn) { // should be a number now if (typeof theSize === 'number') { - // set it in `this.states` if its been changed if (this.states.textSize !== theSize) { this.states.setValue('textSize', theSize); // handle leading here, if not set otherwise if (!this.states.leadingSet) { - this.states.setValue('textLeading', this.states.textSize * LeadingScale); + this.states.setValue( + 'textLeading', + this.states.textSize * LeadingScale + ); } return true; // size was changed } - } - else { + } else { console.warn('textSize: invalid size: ' + theSize); } @@ -2243,9 +2267,12 @@ function textCore(p5, fn) { maxWidth = Infinity, opts = {} ) { - let splitter = opts.splitChar ?? (textWrap === fn.WORD ? ' ' : ''); - let line, testLine, testWidth, words, newLines = []; + let line, + testLine, + testWidth, + words, + newLines = []; for (let lidx = 0; lidx < lines.length; lidx++) { line = ''; @@ -2277,15 +2304,19 @@ function textCore(p5, fn) { Parse the font-family string to handle complex names, fallbacks, etc. */ Renderer.prototype._parseFontFamily = function (familyStr) { - let parts = familyStr.split(CommaDelimRe); - let family = parts.map(part => { - part = part.trim(); - if ((part.indexOf(' ') > -1 || SpecialCharRe.test(part)) && !QuotedRe.test(part)) { - part = `"${part}"`; // quote font names with spaces - } - return part; - }).join(', '); + let family = parts + .map(part => { + part = part.trim(); + if ( + (part.indexOf(' ') > -1 || SpecialCharRe.test(part)) && + !QuotedRe.test(part) + ) { + part = `"${part}"`; // quote font names with spaces + } + return part; + }) + .join(', '); return family; }; @@ -2304,21 +2335,16 @@ function textCore(p5, fn) { - line-height must immediately follow font-size, preceded by "/", eg 16px/3. - font-family must be the last value specified. */ - let { - textFont, - textSize, - lineHeight, - fontStyle, - fontWeight, - fontVariant - } = this.states; + let { textFont, textSize, lineHeight, fontStyle, fontWeight, fontVariant } = + this.states; let drawingContext = this.textDrawingContext(); let family = this._parseFontFamily(textFont.family); let style = fontStyle !== fn.NORMAL ? `${fontStyle} ` : ''; let weight = fontWeight !== fn.NORMAL ? `${fontWeight} ` : ''; let variant = fontVariant !== fn.NORMAL ? `${fontVariant} ` : ''; - let fsize = `${textSize}px` + (lineHeight !== fn.NORMAL ? `/${lineHeight} ` : ' '); + let fsize = + `${textSize}px` + (lineHeight !== fn.NORMAL ? `/${lineHeight} ` : ' '); let fontString = `${style}${variant}${weight}${fsize}${family}`.trim(); //console.log('fontString="' + fontString + '"'); @@ -2343,7 +2369,6 @@ function textCore(p5, fn) { Then apply any properties in the context-queue */ Renderer.prototype._applyTextProperties = function (debug = false) { - this._applyFontString(); // set these after the font so they're not overridden @@ -2360,14 +2385,16 @@ function textCore(p5, fn) { // apply each property in queue after the font so they're not overridden while (contextQueue?.length) { - let [prop, val] = contextQueue.shift(); - if (debug) console.log('apply context property "' + prop + '" = "' + val + '"'); + if (debug) + console.log('apply context property "' + prop + '" = "' + val + '"'); context[prop] = val; // check if the value was set successfully if (context[prop] !== val) { - console.warn(`Unable to set '${prop}' property on context2d. It may not be supported.`); // FES? + console.warn( + `Unable to set '${prop}' property on context2d. It may not be supported.` + ); // FES? console.log('Expected "' + val + '" but got: "' + context[prop] + '"'); } } diff --git a/src/utilities/conversion.js b/src/utilities/conversion.js index 5727735156..8216fe33e9 100644 --- a/src/utilities/conversion.js +++ b/src/utilities/conversion.js @@ -4,7 +4,7 @@ * @for p5 */ -function conversion(p5, fn){ +function conversion(p5, fn) { /** * Converts a `String` to a floating point (decimal) `Number`. * @@ -73,7 +73,7 @@ function conversion(p5, fn){ * @param {String[]} ns array of strings to convert. * @return {Number[]} converted numbers. */ - fn.float = function(str) { + fn.float = function (str) { if (str instanceof Array) { return str.map(parseFloat); } @@ -191,7 +191,7 @@ function conversion(p5, fn){ * @param {Array} ns values to convert. * @return {Number[]} converted numbers. */ - fn.int = function(n, radix = 10) { + fn.int = function (n, radix = 10) { if (n === Infinity || n === 'Infinity') { return Infinity; } else if (n === -Infinity || n === '-Infinity') { @@ -298,7 +298,7 @@ function conversion(p5, fn){ * describe('The text "123456" written in black on a gray background.'); * } */ - fn.str = function(n) { + fn.str = function (n) { if (n instanceof Array) { return n.map(fn.str); } else { @@ -411,7 +411,7 @@ function conversion(p5, fn){ * @param {Array} ns values to convert. * @return {Boolean[]} converted Boolean values. */ - fn.boolean = function(n) { + fn.boolean = function (n) { if (typeof n === 'number') { return n !== 0; } else if (typeof n === 'string') { @@ -545,10 +545,10 @@ function conversion(p5, fn){ * @param {Array} ns values to convert. * @return {Number[]} converted byte values. */ - fn.byte = function(n) { + fn.byte = function (n) { const nn = fn.int(n, 10); if (typeof nn === 'number') { - return (nn + 128) % 256 - 128; + return ((nn + 128) % 256) - 128; } else if (nn instanceof Array) { return nn.map(fn.byte); } @@ -653,7 +653,7 @@ function conversion(p5, fn){ * @param {Array} ns values to convert. * @return {String[]} converted single-character strings. */ - fn.char = function(n) { + fn.char = function (n) { if (typeof n === 'number' && !isNaN(n)) { return String.fromCharCode(n); } else if (n instanceof Array) { @@ -735,7 +735,7 @@ function conversion(p5, fn){ * @param {String[]} ns values to convert. * @return {Number[]} converted numbers. */ - fn.unchar = function(n) { + fn.unchar = function (n) { if (typeof n === 'string' && n.length === 1) { return n.charCodeAt(0); } else if (n instanceof Array) { @@ -847,7 +847,7 @@ function conversion(p5, fn){ * @param {Number} [digits] * @return {String[]} converted hexadecimal values. */ - fn.hex = function(n, digits) { + fn.hex = function (n, digits) { digits = digits === undefined || digits === null ? (digits = 8) : digits; if (n instanceof Array) { return n.map(n => fn.hex(n, digits)); @@ -858,9 +858,7 @@ function conversion(p5, fn){ if (n < 0) { n = 0xffffffff + n + 1; } - let hex = Number(n) - .toString(16) - .toUpperCase(); + let hex = Number(n).toString(16).toUpperCase(); while (hex.length < digits) { hex = `0${hex}`; } @@ -946,7 +944,7 @@ function conversion(p5, fn){ * @param {String[]} ns values to convert. * @return {Number[]} converted numbers. */ - fn.unhex = function(n) { + fn.unhex = function (n) { if (n instanceof Array) { return n.map(fn.unhex); } else { @@ -957,6 +955,6 @@ function conversion(p5, fn){ export default conversion; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { conversion(p5, p5.prototype); } diff --git a/src/utilities/index.js b/src/utilities/index.js index a24cc6e5aa..e2211cef17 100644 --- a/src/utilities/index.js +++ b/src/utilities/index.js @@ -2,7 +2,7 @@ import conversion from './conversion.js'; import utilityFunctions from './utility_functions.js'; import timeDate from './time_date.js'; -export default function(p5){ +export default function (p5) { p5.registerAddon(conversion); p5.registerAddon(utilityFunctions); p5.registerAddon(timeDate); diff --git a/src/utilities/time_date.js b/src/utilities/time_date.js index 5b0b4c154b..43e3c6ff5c 100644 --- a/src/utilities/time_date.js +++ b/src/utilities/time_date.js @@ -4,7 +4,7 @@ * @for p5 */ -function timeDate(p5, fn){ +function timeDate(p5, fn) { /** * Returns the current day as a number from 1–31. * @@ -31,7 +31,7 @@ function timeDate(p5, fn){ * describe(`The text 'Current day: ${d}' written in black on a gray background.`); * } */ - fn.day = function() { + fn.day = function () { return new Date().getDate(); }; @@ -61,7 +61,7 @@ function timeDate(p5, fn){ * describe(`The text 'Current hour: ${h}' written in black on a gray background.`); * } */ - fn.hour = function() { + fn.hour = function () { return new Date().getHours(); }; @@ -91,7 +91,7 @@ function timeDate(p5, fn){ * describe(`The text 'Current minute: ${m}' written in black on a gray background.`); * } */ - fn.minute = function() { + fn.minute = function () { return new Date().getMinutes(); }; @@ -234,7 +234,7 @@ function timeDate(p5, fn){ * @method millis * @return {Number} number of milliseconds since starting the sketch. */ - fn.millis = function() { + fn.millis = function () { if (this._millisStart === -1) { // Sketch has not started return 0; @@ -269,7 +269,7 @@ function timeDate(p5, fn){ * describe(`The text 'Current month: ${m}' written in black on a gray background.`); * } */ - fn.month = function() { + fn.month = function () { //January is 0! return new Date().getMonth() + 1; }; @@ -300,7 +300,7 @@ function timeDate(p5, fn){ * describe(`The text 'Current second: ${s}' written in black on a gray background.`); * } */ - fn.second = function() { + fn.second = function () { return new Date().getSeconds(); }; @@ -330,13 +330,13 @@ function timeDate(p5, fn){ * describe(`The text 'Current year: ${y}' written in black on a gray background.`); * } */ - fn.year = function() { + fn.year = function () { return new Date().getFullYear(); }; } export default timeDate; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { timeDate(p5, p5.prototype); } diff --git a/src/utilities/utility_functions.js b/src/utilities/utility_functions.js index 11964db774..ed8b3faf88 100644 --- a/src/utilities/utility_functions.js +++ b/src/utilities/utility_functions.js @@ -4,7 +4,7 @@ * @for p5 */ -function utilityFunctions(p5, fn){ +function utilityFunctions(p5, fn) { /** * Converts a `Number` into a `String` with a given number of digits. * @@ -85,7 +85,7 @@ function utilityFunctions(p5, fn){ * @param {Integer|String} [right] * @return {String[]} formatted strings. */ - fn.nf = function(nums, left, right) { + fn.nf = function (nums, left, right) { // p5._validateParameters('nf', arguments); if (nums instanceof Array) { return nums.map(x => doNf(x, left, right)); @@ -118,7 +118,10 @@ function utilityFunctions(p5, fn){ let roundedOff = num.toFixed(right); [leftPart, rightPart] = roundedOff.toString().split('.'); leftPart = leftPart.padStart(left, '0'); - let result = typeof rightPart === 'undefined' ? leftPart : leftPart + '.' + rightPart; + let result = + typeof rightPart === 'undefined' + ? leftPart + : leftPart + '.' + rightPart; return isNegative ? '-' + result : result; } } @@ -211,7 +214,7 @@ function utilityFunctions(p5, fn){ * @param {Integer|String} [right] * @return {String[]} formatted strings. */ - fn.nfc = function(num, right) { + fn.nfc = function (num, right) { // p5._validateParameters('nfc', arguments); if (num instanceof Array) { return num.map(x => doNfc(x, right)); @@ -346,7 +349,7 @@ function utilityFunctions(p5, fn){ * @param {Integer} [right] * @return {String[]} formatted strings. */ - fn.nfp = function(...args) { + fn.nfp = function (...args) { // p5._validateParameters('nfp', args); const nfRes = fn.nf.apply(this, args); if (nfRes instanceof Array) { @@ -456,7 +459,7 @@ function utilityFunctions(p5, fn){ * @param {Integer} [right] * @return {String[]} formatted strings. */ - fn.nfs = function(...args) { + fn.nfs = function (...args) { // p5._validateParameters('nfs', args); const nfRes = fn.nf.apply(this, args); if (nfRes instanceof Array) { @@ -599,7 +602,7 @@ function utilityFunctions(p5, fn){ * ); * } */ - fn.splitTokens = function(value, delims) { + fn.splitTokens = function (value, delims) { // p5._validateParameters('splitTokens', arguments); let d; if (typeof delims !== 'undefined') { @@ -737,6 +740,6 @@ function utilityFunctions(p5, fn){ export default utilityFunctions; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { utilityFunctions(p5, p5.prototype); } diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 0d10bfdf4b..890fb808f2 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -10,7 +10,7 @@ import { Vector } from '../math/p5.Vector'; import { Geometry } from './p5.Geometry'; import { Matrix } from '../math/p5.Matrix'; -function primitives3D(p5, fn){ +function primitives3D(p5, fn) { /** * Sets the stroke rendering mode to balance performance and visual features when drawing lines. * @@ -303,7 +303,7 @@ function primitives3D(p5, fn){ * } * } */ - fn.buildGeometry = function(callback) { + fn.buildGeometry = function (callback) { return this._renderer.buildGeometry(callback); }; @@ -392,7 +392,7 @@ function primitives3D(p5, fn){ * } * } */ - fn.freeGeometry = function(geometry) { + fn.freeGeometry = function (geometry) { this._renderer.geometryBufferCache.freeBuffers(geometry.gid); }; @@ -488,12 +488,7 @@ function primitives3D(p5, fn){ * plane(30, 50); * } */ - fn.plane = function( - width = 50, - height = width, - detailX = 1, - detailY = 1 - ) { + fn.plane = function (width = 50, height = width, detailX = 1, detailY = 1) { this._assert3d('plane'); // p5._validateParameters('plane', arguments); @@ -617,7 +612,7 @@ function primitives3D(p5, fn){ * box(30, 50, 10); * } */ - fn.box = function(width, height, depth, detailX, detailY) { + fn.box = function (width, height, depth, detailX, detailY) { this._assert3d('box'); // p5._validateParameters('box', arguments); @@ -736,7 +731,7 @@ function primitives3D(p5, fn){ * sphere(30, 24, 4); * } */ - fn.sphere = function(radius = 50, detailX = 24, detailY = 16) { + fn.sphere = function (radius = 50, detailX = 24, detailY = 16) { this._assert3d('sphere'); // p5._validateParameters('sphere', arguments); @@ -935,7 +930,7 @@ function primitives3D(p5, fn){ * cylinder(30, 50, 24, 1, false, false); * } */ - fn.cylinder = function( + fn.cylinder = function ( radius = 50, height = radius, detailX = 24, @@ -949,8 +944,10 @@ function primitives3D(p5, fn){ this._renderer.cylinder( radius, height, - detailX, detailY, - bottomCap, topCap + detailX, + detailY, + bottomCap, + topCap ); return this; @@ -1139,7 +1136,7 @@ function primitives3D(p5, fn){ * cone(30, 50, 24, 1, false); * } */ - fn.cone = function( + fn.cone = function ( radius = 50, height = radius, detailX = 24, @@ -1306,7 +1303,7 @@ function primitives3D(p5, fn){ * ellipsoid(30, 40, 50, 4, 3); * } */ - fn.ellipsoid = function( + fn.ellipsoid = function ( radiusX = 50, radiusY = radiusX, radiusZ = radiusX, @@ -1455,7 +1452,7 @@ function primitives3D(p5, fn){ * torus(30, 15, 5, 3); * } */ - fn.torus = function(radius, tubeRadius, detailX, detailY) { + fn.torus = function (radius, tubeRadius, detailX, detailY) { this._assert3d('torus'); // p5._validateParameters('torus', arguments); @@ -1499,7 +1496,7 @@ function primitives3D(p5, fn){ * point(0, -25); * } */ - Renderer3D.prototype.point = function(x, y, z = 0) { + Renderer3D.prototype.point = function (x, y, z = 0) { this.beginShape(constants.POINTS); this.vertex(x, y, z); this.endShape(); @@ -1507,7 +1504,7 @@ function primitives3D(p5, fn){ return this; }; - Renderer3D.prototype.triangle = function(args) { + Renderer3D.prototype.triangle = function (args) { const x1 = args[0], y1 = args[1]; const x2 = args[2], @@ -1517,12 +1514,16 @@ function primitives3D(p5, fn){ const gid = 'tri'; if (!this.geometryInHash(gid)) { - const _triangle = function() { + const _triangle = function () { const vertices = []; vertices.push(new Vector(0, 0, 0)); vertices.push(new Vector(1, 0, 0)); vertices.push(new Vector(0, 1, 0)); - this.edges = [[0, 1], [1, 2], [2, 0]]; + this.edges = [ + [0, 1], + [1, 2], + [2, 0] + ]; this.vertices = vertices; this.faces = [[0, 1, 2]]; this.uvs = [0, 0, 1, 0, 1, 1]; @@ -1543,12 +1544,26 @@ function primitives3D(p5, fn){ const uModelMatrix = this.states.uModelMatrix.copy(); try { // triangle orientation. - const orientation = Math.sign(x1*y2-x2*y1 + x2*y3-x3*y2 + x3*y1-x1*y3); + const orientation = Math.sign( + x1 * y2 - x2 * y1 + x2 * y3 - x3 * y2 + x3 * y1 - x1 * y3 + ); const mult = new Matrix([ - x2 - x1, y2 - y1, 0, 0, // the resulting unit X-axis - x3 - x1, y3 - y1, 0, 0, // the resulting unit Y-axis - 0, 0, orientation, 0, // the resulting unit Z-axis (Reflect the specified order of vertices) - x1, y1, 0, 1 // the resulting origin + x2 - x1, + y2 - y1, + 0, + 0, // the resulting unit X-axis + x3 - x1, + y3 - y1, + 0, + 0, // the resulting unit Y-axis + 0, + 0, + orientation, + 0, // the resulting unit Z-axis (Reflect the specified order of vertices) + x1, + y1, + 0, + 1 // the resulting origin ]).mult(this.states.uModelMatrix); this.states.setValue('uModelMatrix', mult); @@ -1561,7 +1576,7 @@ function primitives3D(p5, fn){ return this; }; - Renderer3D.prototype.ellipse = function(args) { + Renderer3D.prototype.ellipse = function (args) { this.arc( args[0], args[1], @@ -1574,7 +1589,7 @@ function primitives3D(p5, fn){ ); }; - Renderer3D.prototype.arc = function(...args) { + Renderer3D.prototype.arc = function (...args) { const x = args[0]; const y = args[1]; const width = args[2]; @@ -1597,8 +1612,7 @@ function primitives3D(p5, fn){ } if (!this.geometryInHash(gid)) { - const _arc = function() { - + const _arc = function () { // if the start and stop angles are not the same, push vertices to the array if (start.toFixed(10) !== stop.toFixed(10)) { // if the mode specified is PIE or null, push the mid point of the arc in vertices @@ -1693,7 +1707,7 @@ function primitives3D(p5, fn){ return this; }; - Renderer3D.prototype.rect = function(args) { + Renderer3D.prototype.rect = function (args) { const x = args[0]; const y = args[1]; const width = args[2]; @@ -1702,12 +1716,13 @@ function primitives3D(p5, fn){ if (typeof args[4] === 'undefined') { // Use the retained mode for drawing rectangle, // if args for rounding rectangle is not provided by user. - const perPixelLighting = this._pInst._glAttributes?.perPixelLighting ?? true; + const perPixelLighting = + this._pInst._glAttributes?.perPixelLighting ?? true; const detailX = args[4] || (perPixelLighting ? 1 : 24); const detailY = args[5] || (perPixelLighting ? 1 : 16); const gid = `rect|${detailX}|${detailY}`; if (!this.geometryInHash(gid)) { - const _rect = function() { + const _rect = function () { for (let i = 0; i <= this.detailY; i++) { const v = i / this.detailY; for (let j = 0; j <= this.detailX; j++) { @@ -1728,10 +1743,7 @@ function primitives3D(p5, fn){ } }; const rectGeom = new Geometry(detailX, detailY, _rect, this); - rectGeom - .computeFaces() - .computeNormals() - ._edgesToVertices(); + rectGeom.computeFaces().computeNormals()._edgesToVertices(); rectGeom.gid = gid; this.geometryBufferCache.ensureCached(rectGeom); } @@ -1794,7 +1806,7 @@ function primitives3D(p5, fn){ const prevOrder = this.bezierOrder(); this.bezierOrder(3); this.beginShape(); - const addUVs = (x, y) => [x, y, 0, (x - x1)/width, (y - y1)/height]; + const addUVs = (x, y) => [x, y, 0, (x - x1) / width, (y - y1) / height]; const rr = 0.5523; // kappa: 4*(sqrt(2)-1)/3, handle ratio for cubic bezier circle approximation if (tr !== 0) { this.vertex(...addUVs(x2 - tr, y1)); @@ -1836,52 +1848,63 @@ function primitives3D(p5, fn){ return this; }; - Renderer3D.prototype.quad = function( - x1, y1, z1, - x2, y2, z2, - x3, y3, z3, - x4, y4, z4, - detailX=2, - detailY=2 + Renderer3D.prototype.quad = function ( + x1, + y1, + z1, + x2, + y2, + z2, + x3, + y3, + z3, + x4, + y4, + z4, + detailX = 2, + detailY = 2 ) { - - const gid = - `quad|${x1}|${y1}|${z1}|${x2}|${y2}|${z2}|${x3}|${y3}|${z3}|${x4}|${y4}|${z4}|${detailX}|${detailY}`; + const gid = `quad|${x1}|${y1}|${z1}|${x2}|${y2}|${z2}|${x3}|${y3}|${z3}|${x4}|${y4}|${z4}|${detailX}|${detailY}`; if (!this.geometryInHash(gid)) { - const quadGeom = new Geometry(detailX, detailY, function() { - //algorithm adapted from c++ to js - //https://stackoverflow.com/questions/16989181/whats-the-correct-way-to-draw-a-distorted-plane-in-opengl/16993202#16993202 - let xRes = 1.0 / (this.detailX - 1); - let yRes = 1.0 / (this.detailY - 1); - for (let y = 0; y < this.detailY; y++) { - for (let x = 0; x < this.detailX; x++) { - let pctx = x * xRes; - let pcty = y * yRes; - - let linePt0x = (1 - pcty) * x1 + pcty * x4; - let linePt0y = (1 - pcty) * y1 + pcty * y4; - let linePt0z = (1 - pcty) * z1 + pcty * z4; - let linePt1x = (1 - pcty) * x2 + pcty * x3; - let linePt1y = (1 - pcty) * y2 + pcty * y3; - let linePt1z = (1 - pcty) * z2 + pcty * z3; - - let ptx = (1 - pctx) * linePt0x + pctx * linePt1x; - let pty = (1 - pctx) * linePt0y + pctx * linePt1y; - let ptz = (1 - pctx) * linePt0z + pctx * linePt1z; - - this.vertices.push(new Vector(ptx, pty, ptz)); - this.uvs.push([pctx, pcty]); + const quadGeom = new Geometry( + detailX, + detailY, + function () { + //algorithm adapted from c++ to js + //https://stackoverflow.com/questions/16989181/whats-the-correct-way-to-draw-a-distorted-plane-in-opengl/16993202#16993202 + let xRes = 1.0 / (this.detailX - 1); + let yRes = 1.0 / (this.detailY - 1); + for (let y = 0; y < this.detailY; y++) { + for (let x = 0; x < this.detailX; x++) { + let pctx = x * xRes; + let pcty = y * yRes; + + let linePt0x = (1 - pcty) * x1 + pcty * x4; + let linePt0y = (1 - pcty) * y1 + pcty * y4; + let linePt0z = (1 - pcty) * z1 + pcty * z4; + let linePt1x = (1 - pcty) * x2 + pcty * x3; + let linePt1y = (1 - pcty) * y2 + pcty * y3; + let linePt1z = (1 - pcty) * z2 + pcty * z3; + + let ptx = (1 - pctx) * linePt0x + pctx * linePt1x; + let pty = (1 - pctx) * linePt0y + pctx * linePt1y; + let ptz = (1 - pctx) * linePt0z + pctx * linePt1z; + + this.vertices.push(new Vector(ptx, pty, ptz)); + this.uvs.push([pctx, pcty]); + } } - } - }, this); + }, + this + ); quadGeom.faces = []; - for(let y = 0; y < detailY-1; y++){ - for(let x = 0; x < detailX-1; x++){ + for (let y = 0; y < detailY - 1; y++) { + for (let x = 0; x < detailX - 1; x++) { let pt0 = x + y * detailX; - let pt1 = (x + 1) + y * detailX; - let pt2 = (x + 1) + (y + 1) * detailX; + let pt1 = x + 1 + y * detailX; + let pt2 = x + 1 + (y + 1) * detailX; let pt3 = x + (y + 1) * detailX; quadGeom.faces.push([pt0, pt1, pt2]); quadGeom.faces.push([pt0, pt2, pt3]); @@ -1906,7 +1929,7 @@ function primitives3D(p5, fn){ //this implementation of bezier curve //is based on Bernstein polynomial // pretier-ignore - Renderer3D.prototype.bezier = function( + Renderer3D.prototype.bezier = function ( x1, y1, z1, // x2 @@ -1941,7 +1964,7 @@ function primitives3D(p5, fn){ }; // pretier-ignore - Renderer3D.prototype.curve = function( + Renderer3D.prototype.curve = function ( x1, y1, z1, // x2 @@ -1997,7 +2020,7 @@ function primitives3D(p5, fn){ * line(10, 10, 0, 60, 60, 20); * } */ - Renderer3D.prototype.line = function(...args) { + Renderer3D.prototype.line = function (...args) { if (args.length === 6) { // TODO shapes refactor this.beginShape(constants.LINES); @@ -2013,7 +2036,7 @@ function primitives3D(p5, fn){ return this; }; - Renderer3D.prototype.image = function( + Renderer3D.prototype.image = function ( img, sx, sy, @@ -2082,7 +2105,7 @@ function primitives3D(p5, fn){ * and topRadius >= 0 * If topRadius == 0, topCap should be false */ - const _truncatedCone = function( + const _truncatedCone = function ( bottomRadius, topRadius, height, @@ -2186,14 +2209,14 @@ function primitives3D(p5, fn){ for (ii = 0; ii < detailX; ++ii) { this.faces.push([ startIndex + ii, - startIndex + (ii + 1) % detailX, + startIndex + ((ii + 1) % detailX), startIndex + detailX ]); } } }; - Renderer3D.prototype.plane = function( + Renderer3D.prototype.plane = function ( width = 50, height = width, detailX = 1, @@ -2202,7 +2225,7 @@ function primitives3D(p5, fn){ const gid = `plane|${detailX}|${detailY}`; if (!this.geometryInHash(gid)) { - const _plane = function() { + const _plane = function () { let u, v, p; for (let i = 0; i <= this.detailY; i++) { v = i / this.detailY; @@ -2221,7 +2244,7 @@ function primitives3D(p5, fn){ } else if (this.states.strokeColor) { console.log( 'Cannot draw stroke on plane objects with more' + - ' than 1 detailX or 1 detailY' + ' than 1 detailX or 1 detailY' ); } planeGeom.gid = gid; @@ -2230,18 +2253,19 @@ function primitives3D(p5, fn){ this._drawGeometryScaled( this.geometryBufferCache.getGeometryByID(gid), - width, height, + width, + height, 1 ); }; - Renderer3D.prototype.box = function( + Renderer3D.prototype.box = function ( width = 50, height = width, depth = height, detailX, detailY - ){ + ) { const perPixelLighting = this.attributes && this.attributes.perPixelLighting; if (typeof detailX === 'undefined') { @@ -2253,7 +2277,7 @@ function primitives3D(p5, fn){ const gid = `box|${detailX}|${detailY}`; if (!this.geometryInHash(gid)) { - const _box = function() { + const _box = function () { const cubeIndices = [ [0, 4, 2, 6], // -1, 0, 0],// -x [1, 3, 5, 7], // +1, 0, 0],// +x @@ -2305,7 +2329,7 @@ function primitives3D(p5, fn){ } else if (this.states.strokeColor) { console.log( 'Cannot draw stroke on box objects with more' + - ' than 4 detailX or 4 detailY' + ' than 4 detailX or 4 detailY' ); } //initialize our geometry buffer with @@ -2316,12 +2340,13 @@ function primitives3D(p5, fn){ } this._drawGeometryScaled( this.geometryBufferCache.getGeometryByID(gid), - width, height, + width, + height, depth ); }; - Renderer3D.prototype.sphere = function( + Renderer3D.prototype.sphere = function ( radius = 50, detailX = 24, detailY = 16 @@ -2329,7 +2354,7 @@ function primitives3D(p5, fn){ this.ellipsoid(radius, radius, radius, detailX, detailY); }; - Renderer3D.prototype.ellipsoid = function( + Renderer3D.prototype.ellipsoid = function ( radiusX = 50, radiusY = radiusX, radiusZ = radiusX, @@ -2339,7 +2364,7 @@ function primitives3D(p5, fn){ const gid = `ellipsoid|${detailX}|${detailY}`; if (!this.geometryInHash(gid)) { - const _ellipsoid = function() { + const _ellipsoid = function () { for (let i = 0; i <= this.detailY; i++) { const v = i / this.detailY; const phi = Math.PI * v - Math.PI / 2; @@ -2369,7 +2394,7 @@ function primitives3D(p5, fn){ } else if (this.states.strokeColor) { console.log( 'Cannot draw stroke on ellipsoids with more' + - ' than 24 detailX or 24 detailY' + ' than 24 detailX or 24 detailY' ); } ellipsoidGeom.gid = gid; @@ -2378,11 +2403,13 @@ function primitives3D(p5, fn){ this._drawGeometryScaled( this.geometryBufferCache.getGeometryByID(gid), - radiusX, radiusY, radiusZ + radiusX, + radiusY, + radiusZ ); }; - Renderer3D.prototype.cylinder = function( + Renderer3D.prototype.cylinder = function ( radius = 50, height = radius, detailX = 24, @@ -2392,25 +2419,30 @@ function primitives3D(p5, fn){ ) { const gid = `cylinder|${detailX}|${detailY}|${bottomCap}|${topCap}`; if (!this.geometryInHash(gid)) { - const cylinderGeom = new p5.Geometry(detailX, detailY, function() { - _truncatedCone.call( - this, - 1, - 1, - 1, - detailX, - detailY, - bottomCap, - topCap - ); - }, this); + const cylinderGeom = new p5.Geometry( + detailX, + detailY, + function () { + _truncatedCone.call( + this, + 1, + 1, + 1, + detailX, + detailY, + bottomCap, + topCap + ); + }, + this + ); // normals are computed in call to _truncatedCone if (detailX <= 24 && detailY <= 16) { cylinderGeom._makeTriangleEdges()._edgesToVertices(); } else if (this.states.strokeColor) { console.log( 'Cannot draw stroke on cylinder objects with more' + - ' than 24 detailX or 16 detailY' + ' than 24 detailX or 16 detailY' ); } cylinderGeom.gid = gid; @@ -2425,7 +2457,7 @@ function primitives3D(p5, fn){ ); }; - Renderer3D.prototype.cone = function( + Renderer3D.prototype.cone = function ( radius = 50, height = radius, detailX = 24, @@ -2434,24 +2466,20 @@ function primitives3D(p5, fn){ ) { const gid = `cone|${detailX}|${detailY}|${cap}`; if (!this.geometryInHash(gid)) { - const coneGeom = new Geometry(detailX, detailY, function() { - _truncatedCone.call( - this, - 1, - 0, - 1, - detailX, - detailY, - cap, - false - ); - }, this); + const coneGeom = new Geometry( + detailX, + detailY, + function () { + _truncatedCone.call(this, 1, 0, 1, detailX, detailY, cap, false); + }, + this + ); if (detailX <= 24 && detailY <= 16) { coneGeom._makeTriangleEdges()._edgesToVertices(); } else if (this.states.strokeColor) { console.log( 'Cannot draw stroke on cone objects with more' + - ' than 24 detailX or 16 detailY' + ' than 24 detailX or 16 detailY' ); } coneGeom.gid = gid; @@ -2466,7 +2494,7 @@ function primitives3D(p5, fn){ ); }; - Renderer3D.prototype.torus = function( + Renderer3D.prototype.torus = function ( radius = 50, tubeRadius = 10, detailX = 24, @@ -2484,7 +2512,7 @@ function primitives3D(p5, fn){ const gid = `torus|${tubeRatio}|${detailX}|${detailY}`; if (!this.geometryInHash(gid)) { - const _torus = function() { + const _torus = function () { for (let i = 0; i <= this.detailY; i++) { const v = i / this.detailY; const phi = 2 * Math.PI * v; @@ -2519,7 +2547,7 @@ function primitives3D(p5, fn){ } else if (this.states.strokeColor) { console.log( 'Cannot draw strokes on torus object with more' + - ' than 24 detailX or 16 detailY' + ' than 24 detailX or 16 detailY' ); } torusGeom.gid = gid; @@ -2587,7 +2615,7 @@ function primitives3D(p5, fn){ * ); * } */ - fn.curveDetail = function(d) { + fn.curveDetail = function (d) { if (!(this._renderer instanceof Renderer3D)) { throw new Error( 'curveDetail() only works in WebGL mode. Did you mean to call createCanvas(width, height, WEBGL)?' @@ -2599,6 +2627,6 @@ function primitives3D(p5, fn){ export default primitives3D; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { primitives3D(p5, p5.prototype); } diff --git a/src/webgl/GeometryBuilder.js b/src/webgl/GeometryBuilder.js index 777feb838e..b3b2d20216 100644 --- a/src/webgl/GeometryBuilder.js +++ b/src/webgl/GeometryBuilder.js @@ -54,8 +54,9 @@ class GeometryBuilder { * transformations. */ addGeometry(input) { - this.hasTransform = !this.renderer.states.uModelMatrix.mat4 - .every((v, i) => v === this.identityMatrix.mat4[i]); + this.hasTransform = !this.renderer.states.uModelMatrix.mat4.every( + (v, i) => v === this.identityMatrix.mat4[i] + ); if (this.hasTransform) { this.renderer.scratchMat3.inverseTranspose4x4( @@ -79,8 +80,8 @@ class GeometryBuilder { const numPreviousVertices = this.geometry.vertices.length - input.vertices.length; - for (const propName in builtUserVertexProps){ - if (propName in inputUserVertexProps){ + for (const propName in builtUserVertexProps) { + if (propName in inputUserVertexProps) { continue; } const prop = builtUserVertexProps[propName]; @@ -89,11 +90,11 @@ class GeometryBuilder { const missingValues = Array(numMissingValues).fill(0); prop.pushDirect(missingValues); } - for (const propName in inputUserVertexProps){ + for (const propName in inputUserVertexProps) { const prop = inputUserVertexProps[propName]; const data = prop.getSrcArray(); const size = prop.getDataSize(); - if (numPreviousVertices > 0 && !(propName in builtUserVertexProps)){ + if (numPreviousVertices > 0 && !(propName in builtUserVertexProps)) { const numMissingValues = size * numPreviousVertices; const missingValues = Array(numMissingValues).fill(0); this.geometry.vertexProperty(propName, missingValues, size); @@ -107,7 +108,9 @@ class GeometryBuilder { ); } if (this.renderer.states.strokeColor) { - for (const edge of input.edges.map(edge => edge.map(idx => idx + startIdx))) { + for (const edge of input.edges.map(edge => + edge.map(idx => idx + startIdx) + )) { this.geometry.edges.push(edge); } } @@ -147,8 +150,10 @@ class GeometryBuilder { for (let i = 0; i < geometry.vertices.length; i += 3) { if ( !validateFaces || - geometry.vertices[i].copy().sub(geometry.vertices[i+1]) - .cross(geometry.vertices[i].copy().sub(geometry.vertices[i+2])) + geometry.vertices[i] + .copy() + .sub(geometry.vertices[i + 1]) + .cross(geometry.vertices[i].copy().sub(geometry.vertices[i + 2])) .magSq() > 0 ) { faces.push([i, i + 1, i + 2]); diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js index 03afb2105b..b5c9201e08 100644 --- a/src/webgl/ShapeBuilder.js +++ b/src/webgl/ShapeBuilder.js @@ -14,8 +14,9 @@ const INITIAL_BUFFER_STRIDES = { // The total number of properties per vertex, before additional // user attributes are added. -const INITIAL_VERTEX_SIZE = - Object.values(INITIAL_BUFFER_STRIDES).reduce((acc, next) => acc + next); +const INITIAL_VERTEX_SIZE = Object.values(INITIAL_BUFFER_STRIDES).reduce( + (acc, next) => acc + next +); export class ShapeBuilder { constructor(renderer) { @@ -50,7 +51,7 @@ export class ShapeBuilder { } constructFromContours(shape, contours) { - if (this._useUserVertexProperties){ + if (this._useUserVertexProperties) { this._resetUserVertexProperties(); } this.geometry.reset(); @@ -162,10 +163,10 @@ export class ShapeBuilder { ) { const proceed = window.confirm( '🌸 p5.js says:\n\n' + - `This shape has ${vertexCount} vertices. Tessellating shapes with this ` + - 'many vertices can be very slow and may cause your browser to become ' + - 'unresponsive.\n\n' + - 'Do you want to continue tessellating this shape?' + `This shape has ${vertexCount} vertices. Tessellating shapes with this ` + + 'many vertices can be very slow and may cause your browser to become ' + + 'unresponsive.\n\n' + + 'Do you want to continue tessellating this shape?' ); if (!proceed) { return; @@ -216,7 +217,9 @@ export class ShapeBuilder { _remapVertices(newIndices) { this.geometry.vertices = newIndices.map(i => this.geometry.vertices[i]); - this.geometry.vertexNormals = newIndices.map(i => this.geometry.vertexNormals[i]); + this.geometry.vertexNormals = newIndices.map( + i => this.geometry.vertexNormals[i] + ); const remapFlat = (arr, stride) => { const result = []; @@ -230,7 +233,10 @@ export class ShapeBuilder { this.geometry.uvs = remapFlat(this.geometry.uvs, 2); this.geometry.vertexColors = remapFlat(this.geometry.vertexColors, 4); - this.geometry.vertexStrokeColors = remapFlat(this.geometry.vertexStrokeColors, 4); + this.geometry.vertexStrokeColors = remapFlat( + this.geometry.vertexStrokeColors, + 4 + ); for (const propName in this.geometry.userVertexProperties) { const prop = this.geometry.userVertexProperties[propName]; @@ -256,7 +262,7 @@ export class ShapeBuilder { _resetUserVertexProperties() { const properties = this.geometry.userVertexProperties; - for (const propName in properties){ + for (const propName in properties) { const prop = properties[propName]; delete this.bufferStrides[propName]; prop.delete(); @@ -272,10 +278,7 @@ export class ShapeBuilder { * @private * @returns {Number[]} indices for custom shape vertices indicating edges. */ - _calculateEdges( - shapeMode, - verts - ) { + _calculateEdges(shapeMode, verts) { const res = []; let i = 0; const contourIndices = this.contourIndices.slice(); @@ -357,14 +360,11 @@ export class ShapeBuilder { // const contours = [[]]; const contours = []; for (let i = 0; i < this.geometry.vertices.length; i++) { - if ( - this.contourIndices.length > 0 && - this.contourIndices[0] === i - ) { + if (this.contourIndices.length > 0 && this.contourIndices[0] === i) { this.contourIndices.shift(); contours.push([]); } - contours[contours.length-1].push( + contours[contours.length - 1].push( this.geometry.vertices[i].x, this.geometry.vertices[i].y, this.geometry.vertices[i].z, @@ -383,7 +383,7 @@ export class ShapeBuilder { const start = i * prop.getDataSize(); const end = start + prop.getDataSize(); const vals = prop.getSrcArray().slice(start, end); - contours[contours.length-1].push(...vals); + contours[contours.length - 1].push(...vals); } } @@ -422,7 +422,7 @@ export class ShapeBuilder { this.geometry.vertices = []; this.geometry.vertexNormals = []; this.geometry.uvs = []; - for (const propName in this.geometry.userVertexProperties){ + for (const propName in this.geometry.userVertexProperties) { const prop = this.geometry.userVertexProperties[propName]; prop.resetSrcArray(); } @@ -438,7 +438,7 @@ export class ShapeBuilder { ); { let offset = 12; - for (const propName in this.geometry.userVertexProperties){ + for (const propName in this.geometry.userVertexProperties) { const prop = this.geometry.userVertexProperties[propName]; const size = prop.getDataSize(); const start = j + offset; @@ -461,15 +461,12 @@ export class ShapeBuilder { // We record index mappings in a Map so that once we have found a // corresponding vertex, we don't need to loop to find it again. const newIndex = new Map(); - this.geometry.edges = - this.geometry.edges.map(edge => edge.map(origIdx => { + this.geometry.edges = this.geometry.edges.map(edge => + edge.map(origIdx => { if (!newIndex.has(origIdx)) { const orig = originalVertices[origIdx]; let newVertIndex = this.geometry.vertices.findIndex( - v => - orig.x === v.x && - orig.y === v.y && - orig.z === v.z + v => orig.x === v.x && orig.y === v.y && orig.z === v.z ); if (newVertIndex === -1) { // The tesselation process didn't output a vertex with the exact @@ -477,16 +474,12 @@ export class ShapeBuilder { // doesn't happen often, but in this case, pick the closest point let closestDist = Infinity; let closestIndex = 0; - for ( - let i = 0; - i < this.geometry.vertices.length; - i++ - ) { + for (let i = 0; i < this.geometry.vertices.length; i++) { const vert = this.geometry.vertices[i]; const dX = orig.x - vert.x; const dY = orig.y - vert.y; const dZ = orig.z - vert.z; - const dist = dX*dX + dY*dY + dZ*dZ; + const dist = dX * dX + dY * dY + dZ * dZ; if (dist < closestDist) { closestDist = dist; closestIndex = i; @@ -497,7 +490,8 @@ export class ShapeBuilder { newIndex.set(origIdx, newVertIndex); } return newIndex.get(origIdx); - })); + }) + ); } this.geometry.vertexColors = colors; } @@ -564,11 +558,7 @@ export class ShapeBuilder { const z = contours[0] ? contours[0][2] : undefined; let allSameZ = true; for (const contour of contours) { - for ( - let j = 0; - j < contour.length; - j += this.tessyVertexSize - ) { + for (let j = 0; j < contour.length; j += this.tessyVertexSize) { if (contour[j + 2] !== z) { allSameZ = false; break; @@ -587,15 +577,8 @@ export class ShapeBuilder { for (const contour of contours) { this._tessy.gluTessBeginContour(); - for ( - let j = 0; - j < contour.length; - j += this.tessyVertexSize - ) { - const coords = contour.slice( - j, - j + this.tessyVertexSize - ); + for (let j = 0; j < contour.length; j += this.tessyVertexSize) { + const coords = contour.slice(j, j + this.tessyVertexSize); this._tessy.gluTessVertex(coords, coords); } this._tessy.gluTessEndContour(); @@ -606,4 +589,4 @@ export class ShapeBuilder { return triangleVerts; } -}; +} diff --git a/src/webgl/enums.js b/src/webgl/enums.js index a93e3dc914..531c9465d3 100644 --- a/src/webgl/enums.js +++ b/src/webgl/enums.js @@ -2,7 +2,7 @@ import * as constants from '../core/constants'; export function getStrokeDefs(shaderConstant) { const STROKE_CAP_ENUM = {}; const STROKE_JOIN_ENUM = {}; - let lineDefs = ""; + let lineDefs = ''; const defineStrokeCapEnum = function (key, val) { lineDefs += shaderConstant(`STROKE_CAP_${key}`, `${val}`, 'u32'); STROKE_CAP_ENUM[constants[key]] = val; @@ -14,12 +14,12 @@ export function getStrokeDefs(shaderConstant) { // Define constants in line shaders for each type of cap/join, and also record // the values in JS objects - defineStrokeCapEnum("ROUND", 0); - defineStrokeCapEnum("PROJECT", 1); - defineStrokeCapEnum("SQUARE", 2); - defineStrokeJoinEnum("ROUND", 0); - defineStrokeJoinEnum("MITER", 1); - defineStrokeJoinEnum("BEVEL", 2); + defineStrokeCapEnum('ROUND', 0); + defineStrokeCapEnum('PROJECT', 1); + defineStrokeCapEnum('SQUARE', 2); + defineStrokeJoinEnum('ROUND', 0); + defineStrokeJoinEnum('MITER', 1); + defineStrokeJoinEnum('BEVEL', 2); return { STROKE_CAP_ENUM, STROKE_JOIN_ENUM, lineDefs }; } diff --git a/src/webgl/index.js b/src/webgl/index.js index 9bf7a3c353..34a7185055 100644 --- a/src/webgl/index.js +++ b/src/webgl/index.js @@ -15,7 +15,7 @@ import texture from './p5.Texture'; import renderer3D from '../core/p5.Renderer3D'; import rendererGL from './p5.RendererGL'; -export default function(p5){ +export default function (p5) { p5.registerAddon(renderer3D); p5.registerAddon(rendererGL); p5.registerAddon(primitives3D); diff --git a/src/webgl/interaction.js b/src/webgl/interaction.js index 669ff75553..b1ddca57ce 100644 --- a/src/webgl/interaction.js +++ b/src/webgl/interaction.js @@ -7,7 +7,7 @@ import * as constants from '../core/constants'; import { Vector } from '../math/p5.Vector'; -function interaction(p5, fn){ +function interaction(p5, fn) { /** * Allows the user to orbit around a 3D sketch using a mouse, trackpad, or * touchscreen. @@ -151,7 +151,7 @@ function interaction(p5, fn){ // implementation based on three.js 'orbitControls': // https://github.com/mrdoob/three.js/blob/6afb8595c0bf8b2e72818e42b64e6fe22707d896/examples/jsm/controls/OrbitControls.js#L22 - fn.orbitControl = function( + fn.orbitControl = function ( sensitivityX, sensitivityY, sensitivityZ, @@ -253,13 +253,15 @@ function interaction(p5, fn){ // for touch, it is calculated based on one moved touch pointer position. pointersInCanvas = - movedTouches[0].x > 0 && movedTouches[0].x < this.width && - movedTouches[0].y > 0 && movedTouches[0].y < this.height; + movedTouches[0].x > 0 && + movedTouches[0].x < this.width && + movedTouches[0].y > 0 && + movedTouches[0].y < this.height; if (movedTouches.length === 1) { const t = movedTouches[0]; - deltaTheta = -sensitivityX * (t.x - t.px) / scaleFactor; - deltaPhi = sensitivityY * (t.y - t.py) / scaleFactor; + deltaTheta = (-sensitivityX * (t.x - t.px)) / scaleFactor; + deltaPhi = (sensitivityY * (t.y - t.py)) / scaleFactor; } else { const t0 = movedTouches[0]; const t1 = movedTouches[1]; @@ -293,8 +295,10 @@ function interaction(p5, fn){ // For mouse, it is calculated based on the mouse position. pointersInCanvas = - (this.mouseX > 0 && this.mouseX < this.width) && - (this.mouseY > 0 && this.mouseY < this.height); + this.mouseX > 0 && + this.mouseX < this.width && + this.mouseY > 0 && + this.mouseY < this.height; if (this._mouseWheelDeltaY !== 0) { // zoom the camera depending on the value of _mouseWheelDeltaY. @@ -310,11 +314,11 @@ function interaction(p5, fn){ } if (this.mouseIsPressed) { if (this.mouseButton.left) { - deltaTheta = -sensitivityX * this.movedX / scaleFactor; - deltaPhi = sensitivityY * this.movedY / scaleFactor; + deltaTheta = (-sensitivityX * this.movedX) / scaleFactor; + deltaPhi = (sensitivityY * this.movedY) / scaleFactor; } else if (this.mouseButton.right) { moveDeltaX = this.movedX; - moveDeltaY = this.movedY * cam.yScale; + moveDeltaY = this.movedY * cam.yScale; } // start rotate and move when mouse is pressed within the canvas. if (pointersInCanvas) this._renderer.executeRotateAndMove = true; @@ -334,26 +338,21 @@ function interaction(p5, fn){ if (Math.abs(this._renderer.zoomVelocity) > 0.001) { // if freeRotation is true, we use _orbitFree() instead of _orbit() if (freeRotation) { - cam._orbitFree( - 0, 0, this._renderer.zoomVelocity - ); + cam._orbitFree(0, 0, this._renderer.zoomVelocity); } else { - cam._orbit( - 0, 0, this._renderer.zoomVelocity - ); + cam._orbit(0, 0, this._renderer.zoomVelocity); } // In orthogonal projection, the scale does not change even if // the distance to the gaze point is changed, so the projection matrix // needs to be modified. if (cam.projMatrix.mat4[15] !== 0) { - cam.projMatrix.mat4[0] *= Math.pow( - 10, -this._renderer.zoomVelocity - ); - cam.projMatrix.mat4[5] *= Math.pow( - 10, -this._renderer.zoomVelocity - ); + cam.projMatrix.mat4[0] *= Math.pow(10, -this._renderer.zoomVelocity); + cam.projMatrix.mat4[5] *= Math.pow(10, -this._renderer.zoomVelocity); // modify uPMatrix - this._renderer.states.setValue('uPMatrix', this._renderer.states.uPMatrix.clone()); + this._renderer.states.setValue( + 'uPMatrix', + this._renderer.states.uPMatrix.clone() + ); this._renderer.states.uPMatrix.mat4[0] = cam.projMatrix.mat4[0]; this._renderer.states.uPMatrix.mat4[5] = cam.projMatrix.mat4[5]; } @@ -364,8 +363,10 @@ function interaction(p5, fn){ } // rotate process - if ((deltaTheta !== 0 || deltaPhi !== 0) && - this._renderer.executeRotateAndMove) { + if ( + (deltaTheta !== 0 || deltaPhi !== 0) && + this._renderer.executeRotateAndMove + ) { // accelerate rotate velocity this._renderer.rotateVelocity.add( deltaTheta * rotateAccelerationFactor, @@ -393,17 +394,18 @@ function interaction(p5, fn){ // damping this._renderer.rotateVelocity.mult(damping); //console.log("multiplied", damping, this._renderer.rotateVelocity); - } else { this._renderer.rotateVelocity.set(0, 0, 0); } // move process - if ((moveDeltaX !== 0 || moveDeltaY !== 0) && - this._renderer.executeRotateAndMove) { + if ( + (moveDeltaX !== 0 || moveDeltaY !== 0) && + this._renderer.executeRotateAndMove + ) { // Normalize movement distance - const ndcX = moveDeltaX * 2/this.width; - const ndcY = -moveDeltaY * 2/this.height; + const ndcX = (moveDeltaX * 2) / this.width; + const ndcY = (-moveDeltaY * 2) / this.height; // accelerate move velocity this._renderer.moveVelocity.add( ndcX * moveAccelerationFactor, @@ -441,11 +443,11 @@ function interaction(p5, fn){ const uP = this._renderer.states.uPMatrix.mat4; if (uP[15] === 0) { - dx = ((uP[8] + cv.x)/uP[0]) * viewZ; - dy = ((uP[9] + cv.y)/uP[5]) * viewZ; + dx = ((uP[8] + cv.x) / uP[0]) * viewZ; + dy = ((uP[9] + cv.y) / uP[5]) * viewZ; } else { - dx = (cv.x - uP[12])/uP[0]; - dy = (cv.y - uP[13])/uP[5]; + dx = (cv.x - uP[12]) / uP[0]; + dy = (cv.y - uP[13]) / uP[5]; } // translate the camera. @@ -463,7 +465,6 @@ function interaction(p5, fn){ return this; }; - /** * Adds a grid and an axes icon to clarify orientation in 3D sketches. * @@ -667,7 +668,7 @@ function interaction(p5, fn){ * @param {Number} [axesZOff] axes icon offset from the origin along the z-axis. */ - fn.debugMode = function(...args) { + fn.debugMode = function (...args) { this._assert3d('debugMode'); // p5._validateParameters('debugMode', args); @@ -735,7 +736,7 @@ function interaction(p5, fn){ * noDebugMode(); * } */ - fn.noDebugMode = function() { + fn.noDebugMode = function () { this._assert3d('noDebugMode'); // start by removing existing 'post' registered debug methods @@ -760,7 +761,7 @@ function interaction(p5, fn){ * @param {Number} [yOff] offset of grid center from origin in Y axis * @param {Number} [zOff] offset of grid center from origin in Z axis */ - fn._grid = function(size, numDivs, xOff, yOff, zOff) { + fn._grid = function (size, numDivs, xOff, yOff, zOff) { if (typeof size === 'undefined') { size = this.width / 2; } @@ -781,14 +782,17 @@ function interaction(p5, fn){ const spacing = size / numDivs; const halfSize = size / 2; - return function() { + return function () { this.push(); this.stroke( this._renderer.states.curStrokeColor[0] * 255, this._renderer.states.curStrokeColor[1] * 255, this._renderer.states.curStrokeColor[2] * 255 ); - this._renderer.states.setValue('uModelMatrix', this._renderer.states.uModelMatrix.clone()); + this._renderer.states.setValue( + 'uModelMatrix', + this._renderer.states.uModelMatrix.clone() + ); this._renderer.states.uModelMatrix.reset(); // Lines along X axis @@ -820,7 +824,7 @@ function interaction(p5, fn){ * @param {Number} [yOff] offset of icon from origin in Y axis * @param {Number} [zOff] offset of icon from origin in Z axis */ - fn._axesIcon = function(size, xOff, yOff, zOff) { + fn._axesIcon = function (size, xOff, yOff, zOff) { if (typeof size === 'undefined') { size = this.width / 20 > 40 ? this.width / 20 : 40; } @@ -836,7 +840,10 @@ function interaction(p5, fn){ return () => { this.push(); - this._renderer.states.setValue('uModelMatrix', this._renderer.states.uModelMatrix.clone()); + this._renderer.states.setValue( + 'uModelMatrix', + this._renderer.states.uModelMatrix.clone() + ); this._renderer.states.uModelMatrix.reset(); // X axis @@ -865,6 +872,6 @@ function interaction(p5, fn){ export default interaction; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { interaction(p5, p5.prototype); } diff --git a/src/webgl/light.js b/src/webgl/light.js index 1088c909e1..83e4200c57 100644 --- a/src/webgl/light.js +++ b/src/webgl/light.js @@ -8,7 +8,7 @@ import { Renderer3D } from '../core/p5.Renderer3D'; import { Vector } from '../math/p5.Vector'; import { Color } from '../color/p5.Color'; -function light(p5, fn){ +function light(p5, fn) { /** * Creates a light that shines from all directions. * @@ -1356,11 +1356,12 @@ function light(p5, fn){ return this; }; - - Renderer3D.prototype.ambientLight = function(v1, v2, v3, a) { + Renderer3D.prototype.ambientLight = function (v1, v2, v3, a) { const color = this._pInst.color(...arguments); - this.states.setValue('ambientLightColors', [...this.states.ambientLightColors]); + this.states.setValue('ambientLightColors', [ + ...this.states.ambientLightColors + ]); this.states.ambientLightColors.push( color._array[0], color._array[1], @@ -1370,7 +1371,7 @@ function light(p5, fn){ this.states.setValue('enableLighting', true); }; - Renderer3D.prototype.specularColor = function(v1, v2, v3) { + Renderer3D.prototype.specularColor = function (v1, v2, v3) { const color = this._pInst.color(...arguments); this.states.setValue('specularColors', [ @@ -1380,7 +1381,7 @@ function light(p5, fn){ ]); }; - Renderer3D.prototype.directionalLight = function(v1, v2, v3, x, y, z) { + Renderer3D.prototype.directionalLight = function (v1, v2, v3, x, y, z) { let color; if (v1 instanceof Color) { color = v1; @@ -1402,17 +1403,23 @@ function light(p5, fn){ // normalize direction const l = Math.sqrt(_x * _x + _y * _y + _z * _z); - this.states.setValue('directionalLightDirections', [...this.states.directionalLightDirections]); + this.states.setValue('directionalLightDirections', [ + ...this.states.directionalLightDirections + ]); this.states.directionalLightDirections.push(_x / l, _y / l, _z / l); - this.states.setValue('directionalLightDiffuseColors', [...this.states.directionalLightDiffuseColors]); + this.states.setValue('directionalLightDiffuseColors', [ + ...this.states.directionalLightDiffuseColors + ]); this.states.directionalLightDiffuseColors.push( color._array[0], color._array[1], color._array[2] ); - this.states.setValue('directionalLightSpecularColors', [...this.states.directionalLightSpecularColors]); + this.states.setValue('directionalLightSpecularColors', [ + ...this.states.directionalLightSpecularColors + ]); Array.prototype.push.apply( this.states.directionalLightSpecularColors, this.states.specularColors @@ -1421,7 +1428,7 @@ function light(p5, fn){ this.states.setValue('enableLighting', true); }; - Renderer3D.prototype.pointLight = function(v1, v2, v3, x, y, z) { + Renderer3D.prototype.pointLight = function (v1, v2, v3, x, y, z) { let color; if (v1 instanceof Color) { color = v1; @@ -1441,17 +1448,23 @@ function light(p5, fn){ _z = v.z; } - this.states.setValue('pointLightPositions', [...this.states.pointLightPositions]); + this.states.setValue('pointLightPositions', [ + ...this.states.pointLightPositions + ]); this.states.pointLightPositions.push(_x, _y, _z); - this.states.setValue('pointLightDiffuseColors', [...this.states.pointLightDiffuseColors]); + this.states.setValue('pointLightDiffuseColors', [ + ...this.states.pointLightDiffuseColors + ]); this.states.pointLightDiffuseColors.push( color._array[0], color._array[1], color._array[2] ); - this.states.setValue('pointLightSpecularColors', [...this.states.pointLightSpecularColors]); + this.states.setValue('pointLightSpecularColors', [ + ...this.states.pointLightSpecularColors + ]); Array.prototype.push.apply( this.states.pointLightSpecularColors, this.states.specularColors @@ -1460,7 +1473,7 @@ function light(p5, fn){ this.states.setValue('enableLighting', true); }; - Renderer3D.prototype.imageLight = function(img) { + Renderer3D.prototype.imageLight = function (img) { // activeImageLight property is checked by _setFillUniforms // for sending uniforms to the fillshader this.states.setValue('activeImageLight', img); @@ -1470,13 +1483,13 @@ function light(p5, fn){ this.makeSpecularTexture(img); }; - Renderer3D.prototype.lights = function() { + Renderer3D.prototype.lights = function () { const grayColor = this._pInst.color('rgb(128,128,128)'); this.ambientLight(grayColor); this.directionalLight(grayColor, 0, 0, -1); }; - Renderer3D.prototype.lightFalloff = function( + Renderer3D.prototype.lightFalloff = function ( constantAttenuation, linearAttenuation, quadraticAttenuation @@ -1504,7 +1517,8 @@ function light(p5, fn){ if ( constantAttenuation === 0 && - (linearAttenuation === 0 && quadraticAttenuation === 0) + linearAttenuation === 0 && + quadraticAttenuation === 0 ) { constantAttenuation = 1; console.warn( @@ -1517,7 +1531,7 @@ function light(p5, fn){ this.states.setValue('quadraticAttenuation', quadraticAttenuation); }; - Renderer3D.prototype.spotLight = function( + Renderer3D.prototype.spotLight = function ( v1, v2, v3, @@ -1728,13 +1742,19 @@ function light(p5, fn){ } angle = this._pInst._toRadians(angle); - this.states.setValue('spotLightAngle', [...this.states.spotLightAngle, Math.cos(angle)]); - this.states.setValue('spotLightConc', [...this.states.spotLightConc, concentration]); + this.states.setValue('spotLightAngle', [ + ...this.states.spotLightAngle, + Math.cos(angle) + ]); + this.states.setValue('spotLightConc', [ + ...this.states.spotLightConc, + concentration + ]); this.states.setValue('enableLighting', true); }; - Renderer3D.prototype.noLights = function() { + Renderer3D.prototype.noLights = function () { this.states.setValue('activeImageLight', null); this.states.setValue('enableLighting', false); @@ -1766,6 +1786,6 @@ function light(p5, fn){ export default light; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { light(p5, p5.prototype); } diff --git a/src/webgl/loading.js b/src/webgl/loading.js index 5883547f03..1fd5126a51 100755 --- a/src/webgl/loading.js +++ b/src/webgl/loading.js @@ -17,7 +17,7 @@ async function fileExists(url) { } } -function loading(p5, fn){ +function loading(p5, fn) { /** * Loads a 3D model to create a * p5.Geometry object. @@ -337,32 +337,32 @@ function loading(p5, fn){ fileType = fileType.fileType || fileType; flipU = fileType.flipU || false; flipV = fileType.flipV || false; - } else { // Passing in individual parameters - if(typeof arguments[arguments.length-1] === 'function'){ - if(typeof arguments[arguments.length-2] === 'function'){ - successCallback = arguments[arguments.length-2]; - failureCallback = arguments[arguments.length-1]; - }else{ - successCallback = arguments[arguments.length-1]; + if (typeof arguments[arguments.length - 1] === 'function') { + if (typeof arguments[arguments.length - 2] === 'function') { + successCallback = arguments[arguments.length - 2]; + failureCallback = arguments[arguments.length - 1]; + } else { + successCallback = arguments[arguments.length - 1]; } } if (typeof fileType === 'string') { - if(typeof normalize !== 'boolean') normalize = false; - + if (typeof normalize !== 'boolean') normalize = false; } else if (typeof fileType === 'boolean') { normalize = fileType; fileType = path.slice(-4); - } else { fileType = path.slice(-4); normalize = false; } } - if (fileType.toLowerCase() !== '.obj' && fileType.toLowerCase() !== '.stl') { + if ( + fileType.toLowerCase() !== '.obj' && + fileType.toLowerCase() !== '.stl' + ) { fileType = '.obj'; } @@ -389,18 +389,21 @@ function loading(p5, fn){ } parsedMaterialPromises.push( - fileExists(mtlPath).then(exists => { - if (exists) { - return parseMtl(mtlPath); - } else { - console.warn(`MTL file not found or error in parsing; proceeding without materials: ${mtlPath}`); + fileExists(mtlPath) + .then(exists => { + if (exists) { + return parseMtl(mtlPath); + } else { + console.warn( + `MTL file not found or error in parsing; proceeding without materials: ${mtlPath}` + ); + return {}; + } + }) + .catch(error => { + console.warn(`Error loading MTL file: ${mtlPath}`, error); return {}; - - } - }).catch(error => { - console.warn(`Error loading MTL file: ${mtlPath}`, error); - return {}; - }) + }) ); } } @@ -414,7 +417,7 @@ function loading(p5, fn){ } } - try{ + try { if (fileType.match(/\.stl$/i)) { const { data } = await request(path, 'arrayBuffer'); const cb = () => { @@ -440,7 +443,6 @@ function loading(p5, fn){ } }; return this._internal ? this._internal(cb) : cb(); - } else if (fileType.match(/\.obj$/i)) { const { data } = await request(path, 'text'); const lines = data.split('\n'); @@ -468,9 +470,9 @@ function loading(p5, fn){ }; return this._internal ? this._internal(cb) : cb(); } - } catch(err) { + } catch (err) { // p5._friendlyFileLoadError(3, path); - if(failureCallback) { + if (failureCallback) { return failureCallback(err); } else { throw err; @@ -515,7 +517,6 @@ function loading(p5, fn){ parseFloat(tokens[2]), parseFloat(tokens[3]) ]; - } else if (tokens[0] === 'map_Kd') { //Texture path materials[currentMaterial].texturePath = tokens[1]; @@ -553,7 +554,6 @@ function loading(p5, fn){ vn: [] }; - // Map from source index → Map of material → destination index const usedVerts = {}; // Track colored vertices let currentMaterial = null; @@ -610,16 +610,24 @@ function loading(p5, fn){ if (usedVerts[vertString][currentMaterial] === undefined) { const vertIndex = model.vertices.length; model.vertices.push(loadedVerts.v.at(vertParts[0]).copy()); - model.uvs.push(loadedVerts.vt.at(vertParts[1]) ? - loadedVerts.vt.at(vertParts[1]).slice() : [0, 0]); - model.vertexNormals.push(loadedVerts.vn.at(vertParts[2]) ? - loadedVerts.vn.at(vertParts[2]).copy() : new Vector(0, 0, 0)); + model.uvs.push( + loadedVerts.vt.at(vertParts[1]) + ? loadedVerts.vt.at(vertParts[1]).slice() + : [0, 0] + ); + model.vertexNormals.push( + loadedVerts.vn.at(vertParts[2]) + ? loadedVerts.vn.at(vertParts[2]).copy() + : new Vector(0, 0, 0) + ); usedVerts[vertString][currentMaterial] = vertIndex; face.push(vertIndex); - if (currentMaterial - && materials[currentMaterial] - && materials[currentMaterial].diffuseColor) { + if ( + currentMaterial && + materials[currentMaterial] && + materials[currentMaterial].diffuseColor + ) { hasColoredVertices = true; const materialDiffuseColor = materials[currentMaterial].diffuseColor; @@ -888,7 +896,9 @@ function loading(p5, fn){ if (parts[0] !== 'outer' || parts[1] !== 'loop') { // Invalid State console.error(line); - console.error(`Invalid state "${parts[0]}", should be "outer loop"`); + console.error( + `Invalid state "${parts[0]}", should be "outer loop"` + ); return; } else { // Next should be vertices @@ -949,7 +959,8 @@ function loading(p5, fn){ // Invalid State console.error(line); console.error( - `Invalid state "${parts[0] + `Invalid state "${ + parts[0] }", should be "endsolid" or "facet normal"` ); return; @@ -1214,9 +1225,9 @@ function loading(p5, fn){ * @return {p5.Geometry} the p5.Geometry object */ let modelCounter = 0; - fn.createModel = function(modelString, fileType=' ', options) { + fn.createModel = function (modelString, fileType = ' ', options) { // p5._validateParameters('createModel', arguments); - let normalize= false; + let normalize = false; let successCallback; let failureCallback; let flipU = false; @@ -1232,7 +1243,8 @@ function loading(p5, fn){ successCallback = arguments[3]; failureCallback = arguments[4]; } else { - successCallback = typeof arguments[2] === 'function' ? arguments[2] : undefined; + successCallback = + typeof arguments[2] === 'function' ? arguments[2] : undefined; failureCallback = arguments[3]; } const model = new p5.Geometry(); @@ -1268,7 +1280,8 @@ function loading(p5, fn){ if (failureCallback) { failureCallback(); } else { - p5.FES.log`Sorry, the file type is invalid. Only OBJ and STL files are supported.`(); + p5.FES + .log`Sorry, the file type is invalid. Only OBJ and STL files are supported.`(); } } if (normalize) { @@ -1295,6 +1308,6 @@ function loading(p5, fn){ export default loading; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { loading(p5, p5.prototype); } diff --git a/src/webgl/material.js b/src/webgl/material.js index 07f43244eb..5c9f5e8809 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -4,14 +4,14 @@ * @for p5 */ -import * as constants from "../core/constants"; -import { Renderer3D } from "../core/p5.Renderer3D"; -import { Shader } from "./p5.Shader"; -import { request } from "../io/files"; -import { Color } from "../color/p5.Color"; +import * as constants from '../core/constants'; +import { Renderer3D } from '../core/p5.Renderer3D'; +import { Shader } from './p5.Shader'; +import { request } from '../io/files'; +import { Color } from '../color/p5.Color'; async function urlToStrandsCallback(url) { - const src = await fetch(url).then((res) => res.text()); + const src = await fetch(url).then(res => res.text()); return new Function(src); } @@ -130,15 +130,15 @@ function material(p5, fn) { vertFilename, fragFilename, successCallback, - failureCallback, + failureCallback ) { // p5._validateParameters('loadShader', arguments); const loadedShader = new Shader(); try { - loadedShader._vertSrc = (await request(vertFilename, "text")).data; - loadedShader._fragSrc = (await request(fragFilename, "text")).data; + loadedShader._vertSrc = (await request(vertFilename, 'text')).data; + loadedShader._fragSrc = (await request(fragFilename, 'text')).data; if (successCallback) { return successCallback(loadedShader) || loadedShader; @@ -513,22 +513,26 @@ function material(p5, fn) { fn.loadFilterShader = async function ( fragFilename, successCallback, - failureCallback, + failureCallback ) { // p5._validateParameters('loadFilterShader', arguments); try { // Load the fragment shader const fragSrc = await this.loadStrings(fragFilename); - const fragString = await fragSrc.join("\n"); + const fragString = await fragSrc.join('\n'); // Test if we've loaded GLSL or not by checking for the existence of `void main` let loadedShader; if (/void\s+main/.exec(fragString)) { - loadedShader = this._internal(() => this.createFilterShader(fragString, true)); + loadedShader = this._internal(() => + this.createFilterShader(fragString, true) + ); } else { - loadedShader = this._internal(() => withGlobalStrands(this, () => - this.baseFilterShader().modify(new Function(fragString)), - )); + loadedShader = this._internal(() => + withGlobalStrands(this, () => + this.baseFilterShader().modify(new Function(fragString)) + ) + ); } if (successCallback) { @@ -871,7 +875,7 @@ function material(p5, fn) { gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4; } `; - let vertSrc = fragSrc.includes("#version 300 es") + let vertSrc = fragSrc.includes('#version 300 es') ? defaultVertV2 : defaultVertV1; const shader = new Shader(this._renderer, vertSrc, fragSrc); @@ -1227,7 +1231,7 @@ function material(p5, fn) { * } */ fn.strokeShader = function (s) { - this._assert3d("strokeShader"); + this._assert3d('strokeShader'); // p5._validateParameters('strokeShader', arguments); this._renderer.strokeShader(s); @@ -1372,7 +1376,7 @@ function material(p5, fn) { * } */ fn.imageShader = function (s) { - this._assert3d("imageShader"); + this._assert3d('imageShader'); // p5._validateParameters('imageShader', arguments); this._renderer.imageShader(s); @@ -1630,7 +1634,9 @@ function material(p5, fn) { fn.loadMaterialShader = async function (url, onSuccess, onFail) { try { const cb = await urlToStrandsCallback(url); - let shader = this._internal(() => withGlobalStrands(this, () => this.buildMaterialShader(cb))); + let shader = this._internal(() => + withGlobalStrands(this, () => this.buildMaterialShader(cb)) + ); if (onSuccess) { shader = onSuccess(shader) || shader; } @@ -1659,7 +1665,7 @@ function material(p5, fn) { * @returns {p5.Shader} The base material shader. */ fn.baseMaterialShader = function () { - this._assert3d("baseMaterialShader"); + this._assert3d('baseMaterialShader'); return this._renderer.baseMaterialShader(); }; @@ -1847,9 +1853,9 @@ function material(p5, fn) { fn.loadNormalShader = async function (url, onSuccess, onFail) { try { const cb = await urlToStrandsCallback(url); - let shader = this._internal(() => this.withGlobalStrands(this, () => - this.buildNormalShader(cb), - )); + let shader = this._internal(() => + this.withGlobalStrands(this, () => this.buildNormalShader(cb)) + ); if (onSuccess) { shader = onSuccess(shader) || shader; } @@ -1879,7 +1885,7 @@ function material(p5, fn) { * @returns {p5.Shader} The base material shader. */ fn.baseNormalShader = function () { - this._assert3d("baseNormalShader"); + this._assert3d('baseNormalShader'); return this._renderer.baseNormalShader(); }; @@ -2011,7 +2017,9 @@ function material(p5, fn) { fn.loadColorShader = async function (url, onSuccess, onFail) { try { const cb = await urlToStrandsCallback(url); - let shader = this._internal(() => withGlobalStrands(this, () => this.buildColorShader(cb))); + let shader = this._internal(() => + withGlobalStrands(this, () => this.buildColorShader(cb)) + ); if (onSuccess) { shader = onSuccess(shader) || shader; } @@ -2040,7 +2048,7 @@ function material(p5, fn) { * @returns {p5.Shader} The base color shader. */ fn.baseColorShader = function () { - this._assert3d("baseColorShader"); + this._assert3d('baseColorShader'); return this._renderer.baseColorShader(); }; @@ -2271,7 +2279,9 @@ function material(p5, fn) { fn.loadStrokeShader = async function (url, onSuccess, onFail) { try { const cb = await urlToStrandsCallback(url); - let shader = this._internal(() => withGlobalStrands(this, () => this.buildStrokeShader(cb))); + let shader = this._internal(() => + withGlobalStrands(this, () => this.buildStrokeShader(cb)) + ); if (onSuccess) { shader = onSuccess(shader) || shader; } @@ -2300,7 +2310,7 @@ function material(p5, fn) { * @returns {p5.Shader} The base material shader. */ fn.baseStrokeShader = function () { - this._assert3d("baseStrokeShader"); + this._assert3d('baseStrokeShader'); return this._renderer.baseStrokeShader(); }; @@ -2543,7 +2553,7 @@ function material(p5, fn) { * } */ fn.texture = function (tex) { - this._assert3d("texture"); + this._assert3d('texture'); // p5._validateParameters('texture', arguments); // NOTE: make generic or remove need for @@ -2721,15 +2731,16 @@ function material(p5, fn) { * @return {(IMAGE|NORMAL)} The current texture mode, either IMAGE or NORMAL. */ fn.textureMode = function (mode) { - if (typeof mode === 'undefined') { // getter + if (typeof mode === 'undefined') { + // getter return this._renderer.states.textureMode; } if (mode !== constants.IMAGE && mode !== constants.NORMAL) { console.warn( - `You tried to set ${mode} textureMode only supports IMAGE & NORMAL `, + `You tried to set ${mode} textureMode only supports IMAGE & NORMAL ` ); } else { - this._renderer.states.setValue("textureMode", mode); + this._renderer.states.setValue('textureMode', mode); } }; @@ -2994,7 +3005,8 @@ function material(p5, fn) { * @return {{x: (CLAMP|REPEAT|MIRROR), y: (CLAMP|REPEAT|MIRROR)}} The current texture wrapping for x and y. */ fn.textureWrap = function (wrapX, wrapY = wrapX) { - if (typeof wrapX === 'undefined') { // getter + if (typeof wrapX === 'undefined') { + // getter return { x: this._renderer.states.textureWrapX, y: this._renderer.states.textureWrapY @@ -3051,7 +3063,7 @@ function material(p5, fn) { * } */ fn.normalMaterial = function (...args) { - this._assert3d("normalMaterial"); + this._assert3d('normalMaterial'); // p5._validateParameters('normalMaterial', args); this._renderer.normalMaterial(...args); @@ -3257,16 +3269,16 @@ function material(p5, fn) { * @chainable */ fn.ambientMaterial = function (v1, v2, v3) { - this._assert3d("ambientMaterial"); + this._assert3d('ambientMaterial'); // p5._validateParameters('ambientMaterial', arguments); const color = fn.color.apply(this, arguments); - this._renderer.states.setValue("_hasSetAmbient", true); - this._renderer.states.setValue("curAmbientColor", color._array); - this._renderer.states.setValue("_useNormalMaterial", false); - this._renderer.states.setValue("enableLighting", true); + this._renderer.states.setValue('_hasSetAmbient', true); + this._renderer.states.setValue('curAmbientColor', color._array); + this._renderer.states.setValue('_useNormalMaterial', false); + this._renderer.states.setValue('enableLighting', true); if (!this._renderer.states.fillColor) { - this._renderer.states.setValue("fillColor", new Color([1, 1, 1])); + this._renderer.states.setValue('fillColor', new Color([1, 1, 1])); } return this; }; @@ -3349,14 +3361,14 @@ function material(p5, fn) { * @chainable */ fn.emissiveMaterial = function (v1, v2, v3, a) { - this._assert3d("emissiveMaterial"); + this._assert3d('emissiveMaterial'); // p5._validateParameters('emissiveMaterial', arguments); const color = fn.color.apply(this, arguments); - this._renderer.states.setValue("curEmissiveColor", color._array); - this._renderer.states.setValue("_useEmissiveMaterial", true); - this._renderer.states.setValue("_useNormalMaterial", false); - this._renderer.states.setValue("enableLighting", true); + this._renderer.states.setValue('curEmissiveColor', color._array); + this._renderer.states.setValue('_useEmissiveMaterial', true); + this._renderer.states.setValue('_useNormalMaterial', false); + this._renderer.states.setValue('enableLighting', true); return this; }; @@ -3589,14 +3601,14 @@ function material(p5, fn) { * @chainable */ fn.specularMaterial = function (v1, v2, v3, alpha) { - this._assert3d("specularMaterial"); + this._assert3d('specularMaterial'); // p5._validateParameters('specularMaterial', arguments); const color = fn.color.apply(this, arguments); - this._renderer.states.setValue("curSpecularColor", color._array); - this._renderer.states.setValue("_useSpecularMaterial", true); - this._renderer.states.setValue("_useNormalMaterial", false); - this._renderer.states.setValue("enableLighting", true); + this._renderer.states.setValue('curSpecularColor', color._array); + this._renderer.states.setValue('_useSpecularMaterial', true); + this._renderer.states.setValue('_useNormalMaterial', false); + this._renderer.states.setValue('enableLighting', true); return this; }; @@ -3658,7 +3670,7 @@ function material(p5, fn) { * } */ fn.shininess = function (shine) { - this._assert3d("shininess"); + this._assert3d('shininess'); // p5._validateParameters('shininess', arguments); this._renderer.shininess(shine); @@ -3767,7 +3779,7 @@ function material(p5, fn) { * } */ fn.metalness = function (metallic) { - this._assert3d("metalness"); + this._assert3d('metalness'); this._renderer.metalness(metallic); @@ -3776,45 +3788,45 @@ function material(p5, fn) { Renderer3D.prototype.shader = function (s) { // Always set the shader as a fill shader - this.states.setValue("userFillShader", s); - this.states.setValue("_useNormalMaterial", false); + this.states.setValue('userFillShader', s); + this.states.setValue('_useNormalMaterial', false); s.ensureCompiledOnContext(this); s.setDefaultUniforms(); }; Renderer3D.prototype.strokeShader = function (s) { - this.states.setValue("userStrokeShader", s); + this.states.setValue('userStrokeShader', s); s.ensureCompiledOnContext(this); s.setDefaultUniforms(); }; Renderer3D.prototype.imageShader = function (s) { - this.states.setValue("userImageShader", s); + this.states.setValue('userImageShader', s); s.ensureCompiledOnContext(this); s.setDefaultUniforms(); }; Renderer3D.prototype.resetShader = function () { - this.states.setValue("userFillShader", null); - this.states.setValue("userStrokeShader", null); - this.states.setValue("userImageShader", null); + this.states.setValue('userFillShader', null); + this.states.setValue('userStrokeShader', null); + this.states.setValue('userImageShader', null); }; Renderer3D.prototype.texture = function (tex) { - this.states.setValue("drawMode", constants.TEXTURE); - this.states.setValue("_useNormalMaterial", false); - this.states.setValue("_tex", tex); - this.states.setValue("fillColor", new Color([1, 1, 1])); + this.states.setValue('drawMode', constants.TEXTURE); + this.states.setValue('_useNormalMaterial', false); + this.states.setValue('_tex', tex); + this.states.setValue('fillColor', new Color([1, 1, 1])); }; Renderer3D.prototype.normalMaterial = function (...args) { - this.states.setValue("drawMode", constants.FILL); - this.states.setValue("_useSpecularMaterial", false); - this.states.setValue("_useEmissiveMaterial", false); - this.states.setValue("_useNormalMaterial", true); - this.states.setValue("curFillColor", [1, 1, 1, 1]); - this.states.setValue("fillColor", new Color([1, 1, 1])); - this.states.setValue("strokeColor", null); + this.states.setValue('drawMode', constants.FILL); + this.states.setValue('_useSpecularMaterial', false); + this.states.setValue('_useEmissiveMaterial', false); + this.states.setValue('_useNormalMaterial', true); + this.states.setValue('curFillColor', [1, 1, 1, 1]); + this.states.setValue('fillColor', new Color([1, 1, 1])); + this.states.setValue('strokeColor', null); }; // Renderer3D.prototype.ambientMaterial = function(v1, v2, v3) { @@ -3830,17 +3842,17 @@ function material(p5, fn) { if (shine < 1) { shine = 1; } - this.states.setValue("_useShininess", shine); + this.states.setValue('_useShininess', shine); }; Renderer3D.prototype.metalness = function (metallic) { const metalMix = 1 - Math.exp(-metallic / 100); - this.states.setValue("_useMetalness", metalMix); + this.states.setValue('_useMetalness', metalMix); }; } export default material; -if (typeof p5 !== "undefined") { +if (typeof p5 !== 'undefined') { loading(p5, p5.prototype); } diff --git a/src/webgl/p5.Camera.js b/src/webgl/p5.Camera.js index 38929118fe..deff373ac9 100644 --- a/src/webgl/p5.Camera.js +++ b/src/webgl/p5.Camera.js @@ -207,14 +207,14 @@ class Camera { near = 0.01; console.log( 'Avoid perspective near plane values close to or below 0. ' + - 'Setting value to 0.01.' + 'Setting value to 0.01.' ); } if (far < near) { console.log( 'Perspective far plane value is less than near plane value. ' + - 'Nothing will be shown.' + 'Nothing will be shown.' ); } @@ -235,16 +235,33 @@ class Camera { } else { // WebGL clip space, z in [-1, 1] A = (far + near) * nf; - B = (2 * far * near) * nf; + B = 2 * far * near * nf; } - this.projMatrix.set(f / aspect, 0, 0, 0, - 0, -f * this.yScale, 0, 0, - 0, 0, A, -1, - 0, 0, B, 0); + this.projMatrix.set( + f / aspect, + 0, + 0, + 0, + 0, + -f * this.yScale, + 0, + 0, + 0, + 0, + A, + -1, + 0, + 0, + B, + 0 + ); if (this._isActive()) { - this._renderer.states.setValue('uPMatrix', this._renderer.states.uPMatrix.clone()); + this._renderer.states.setValue( + 'uPMatrix', + this._renderer.states.uPMatrix.clone() + ); this._renderer.states.uPMatrix.set(this.projMatrix); } } @@ -412,20 +429,20 @@ class Camera { const h = top - bottom; const d = far - near; const x = +2.0 / w; - const y = +2.0 / h * this.yScale; + const y = (+2.0 / h) * this.yScale; const z = -2.0 / d; const tx = -(right + left) / w; const ty = -(top + bottom) / h; const tz = -(far + near) / d; this.projMatrix = new Matrix(4); - this.projMatrix.set(x, 0, 0, 0, - 0, -y, 0, 0, - 0, 0, z, 0, - tx, ty, tz, 1); + this.projMatrix.set(x, 0, 0, 0, 0, -y, 0, 0, 0, 0, z, 0, tx, ty, tz, 1); if (this._isActive()) { - this._renderer.states.setValue('uPMatrix', this._renderer.states.uPMatrix.clone()); + this._renderer.states.setValue( + 'uPMatrix', + this._renderer.states.uPMatrix.clone() + ); this._renderer.states.uPMatrix.set(this.projMatrix); } this.cameraType = 'custom'; @@ -543,7 +560,7 @@ class Camera { const d = far - near; const x = +(2.0 * near) / w; - const y = +(2.0 * near) / h * this.yScale; + const y = (+(2.0 * near) / h) * this.yScale; const z = -(2.0 * far * near) / d; const tx = (right + left) / w; @@ -552,15 +569,13 @@ class Camera { this.projMatrix = new Matrix(4); - - this.projMatrix.set(x, 0, 0, 0, - 0, -y, 0, 0, - tx, ty, tz, -1, - 0, 0, z, 0); - + this.projMatrix.set(x, 0, 0, 0, 0, -y, 0, 0, tx, ty, tz, -1, 0, 0, z, 0); if (this._isActive()) { - this._renderer.states.setValue('uPMatrix', this._renderer.states.uPMatrix.clone()); + this._renderer.states.setValue( + 'uPMatrix', + this._renderer.states.uPMatrix.clone() + ); this._renderer.states.uPMatrix.set(this.projMatrix); } @@ -590,9 +605,15 @@ class Camera { rotation.rotate4x4(this._renderer._pInst._toRadians(a), x, y, z); const rotatedCenter = [ - centerX * rotation.mat4[0] + centerY * rotation.mat4[4] + centerZ * rotation.mat4[8], - centerX * rotation.mat4[1] + centerY * rotation.mat4[5] + centerZ * rotation.mat4[9], - centerX * rotation.mat4[2] + centerY * rotation.mat4[6] + centerZ * rotation.mat4[10] + centerX * rotation.mat4[0] + + centerY * rotation.mat4[4] + + centerZ * rotation.mat4[8], + centerX * rotation.mat4[1] + + centerY * rotation.mat4[5] + + centerZ * rotation.mat4[9], + centerX * rotation.mat4[2] + + centerY * rotation.mat4[6] + + centerZ * rotation.mat4[10] ]; // add eye position back into center @@ -673,10 +694,14 @@ class Camera { const local = this._getLocalAxes(); const axisQuaternion = Quat.fromAxisAngle( this._renderer._pInst._toRadians(amount), - local.z[0], local.z[1], local.z[2]); + local.z[0], + local.z[1], + local.z[2] + ); // const upQuat = new p5.Quat(0, this.upX, this.upY, this.upZ); const newUpVector = axisQuaternion.rotateVector( - new Vector(this.upX, this.upY, this.upZ)); + new Vector(this.upX, this.upY, this.upZ) + ); this.camera( this.eyeX, this.eyeY, @@ -1064,17 +1089,7 @@ class Camera { * } * } */ - camera( - eyeX, - eyeY, - eyeZ, - centerX, - centerY, - centerZ, - upX, - upY, - upZ - ) { + camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) { if (typeof eyeX === 'undefined') { eyeX = this.defaultEyeX; eyeY = this.defaultEyeY; @@ -1109,11 +1124,24 @@ class Camera { // inverse translates the world to the eye position of the camera // and rotates it. - this.cameraMatrix.set(local.x[0], local.y[0], local.z[0], 0, - local.x[1], local.y[1], local.z[1], 0, - local.x[2], local.y[2], local.z[2], 0, - 0, 0, 0, 1); - + this.cameraMatrix.set( + local.x[0], + local.y[0], + local.z[0], + 0, + local.x[1], + local.y[1], + local.z[1], + 0, + local.x[2], + local.y[2], + local.z[2], + 0, + 0, + 0, + 0, + 1 + ); const tx = -eyeX; const ty = -eyeY; @@ -1122,7 +1150,10 @@ class Camera { this.cameraMatrix.translate([tx, ty, tz]); if (this._isActive()) { - this._renderer.states.setValue('uViewMatrix', this._renderer.states.uViewMatrix.clone()); + this._renderer.states.setValue( + 'uViewMatrix', + this._renderer.states.uViewMatrix.clone() + ); this._renderer.states.uViewMatrix.set(this.cameraMatrix); } return this; @@ -1425,11 +1456,22 @@ class Camera { */ set(cam) { const keyNamesOfThePropToCopy = [ - 'eyeX', 'eyeY', 'eyeZ', - 'centerX', 'centerY', 'centerZ', - 'upX', 'upY', 'upZ', - 'cameraFOV', 'aspectRatio', 'cameraNear', 'cameraFar', 'cameraType', - 'yScale', 'useLinePerspective' + 'eyeX', + 'eyeY', + 'eyeZ', + 'centerX', + 'centerY', + 'centerZ', + 'upX', + 'upY', + 'upZ', + 'cameraFOV', + 'aspectRatio', + 'cameraNear', + 'cameraFar', + 'cameraType', + 'yScale', + 'useLinePerspective' ]; for (const keyName of keyNamesOfThePropToCopy) { this[keyName] = cam[keyName]; @@ -1439,9 +1481,18 @@ class Camera { this.projMatrix = cam.projMatrix.copy(); if (this._isActive()) { - this._renderer.states.setValue('uModelMatrix', this._renderer.states.uModelMatrix.clone()); - this._renderer.states.setValue('uViewMatrix', this._renderer.states.uViewMatrix.clone()); - this._renderer.states.setValue('uPMatrix', this._renderer.states.uPMatrix.clone()); + this._renderer.states.setValue( + 'uModelMatrix', + this._renderer.states.uModelMatrix.clone() + ); + this._renderer.states.setValue( + 'uViewMatrix', + this._renderer.states.uViewMatrix.clone() + ); + this._renderer.states.setValue( + 'uPMatrix', + this._renderer.states.uPMatrix.clone() + ); this._renderer.states.uModelMatrix.reset(); this._renderer.states.uViewMatrix.set(this.cameraMatrix); this._renderer.states.uPMatrix.set(this.projMatrix); @@ -1534,12 +1585,12 @@ class Camera { this.projMatrix.setElement( 0, cam0.projMatrix.mat4[0] * - Math.pow(cam1.projMatrix.mat4[0] / cam0.projMatrix.mat4[0], amt) + Math.pow(cam1.projMatrix.mat4[0] / cam0.projMatrix.mat4[0], amt) ); this.projMatrix.setElement( 5, cam0.projMatrix.mat4[5] * - Math.pow(cam1.projMatrix.mat4[5] / cam0.projMatrix.mat4[5], amt) + Math.pow(cam1.projMatrix.mat4[5] / cam0.projMatrix.mat4[5], amt) ); // If the camera is active, make uPMatrix reflect changes in projMatrix. if (this._isActive()) { @@ -1627,16 +1678,28 @@ class Camera { // calculate newEye, newCenter with newFront vector. newFront.set(Vector.lerp(front0, front1, amt)).normalize(); - newEye.set(newFront).mult(ratio * lerpedDist).add(lerpedMedium); - newCenter.set(newFront).mult((ratio - 1) * lerpedDist).add(lerpedMedium); + newEye + .set(newFront) + .mult(ratio * lerpedDist) + .add(lerpedMedium); + newCenter + .set(newFront) + .mult((ratio - 1) * lerpedDist) + .add(lerpedMedium); newUp.set(Vector.lerp(up0, up1, amt)).normalize(); // set the camera this.camera( - newEye.x, newEye.y, newEye.z, - newCenter.x, newCenter.y, newCenter.z, - newUp.x, newUp.y, newUp.z + newEye.x, + newEye.y, + newEye.z, + newCenter.x, + newCenter.y, + newCenter.z, + newUp.x, + newUp.y, + newUp.z ); return; } @@ -1657,21 +1720,19 @@ class Camera { invOneMinusCosTheta /= a; b = 0.5 * offDiagSum13 * invOneMinusCosTheta; c = 0.5 * offDiagSum26 * invOneMinusCosTheta; - sinTheta = 0.5 * (deltaRot.mat3[7] - deltaRot.mat3[5]) / a; - + sinTheta = (0.5 * (deltaRot.mat3[7] - deltaRot.mat3[5])) / a; } else if (maxDiag === diag[1]) { b = Math.sqrt((diag[1] - cosTheta) * invOneMinusCosTheta); // not zero. invOneMinusCosTheta /= b; c = 0.5 * offDiagSum57 * invOneMinusCosTheta; a = 0.5 * offDiagSum13 * invOneMinusCosTheta; - sinTheta = 0.5 * (deltaRot.mat3[2] - deltaRot.mat3[6]) / b; - + sinTheta = (0.5 * (deltaRot.mat3[2] - deltaRot.mat3[6])) / b; } else { c = Math.sqrt((diag[2] - cosTheta) * invOneMinusCosTheta); // not zero. invOneMinusCosTheta /= c; a = 0.5 * offDiagSum26 * invOneMinusCosTheta; b = 0.5 * offDiagSum57 * invOneMinusCosTheta; - sinTheta = 0.5 * (deltaRot.mat3[3] - deltaRot.mat3[1]) / c; + sinTheta = (0.5 * (deltaRot.mat3[3] - deltaRot.mat3[1])) / c; } // Constructs a new matrix after interpolating the angles. @@ -1686,7 +1747,7 @@ class Camera { const bc = b * c; const ca = c * a; // 3x3 - const lerpedRotMat = new Matrix( [ + const lerpedRotMat = new Matrix([ cosAngle + oneMinusCosAngle * a * a, oneMinusCosAngle * ab + sinAngle * c, oneMinusCosAngle * ca - sinAngle * b, @@ -1702,17 +1763,29 @@ class Camera { // calculate newEye, newCenter with newFront vector. lerpedRotMat.multiplyVec(front0, newFront); // this is vec3 - newEye.set(newFront).mult(ratio * lerpedDist).add(lerpedMedium); - newCenter.set(newFront).mult((ratio - 1) * lerpedDist).add(lerpedMedium); + newEye + .set(newFront) + .mult(ratio * lerpedDist) + .add(lerpedMedium); + newCenter + .set(newFront) + .mult((ratio - 1) * lerpedDist) + .add(lerpedMedium); lerpedRotMat.multiplyVec(up0, newUp); // this is vec3 // We also get the up vector in the same way and set the camera. // The eye position and center position are calculated based on the front vector. this.camera( - newEye.x, newEye.y, newEye.z, - newCenter.x, newCenter.y, newCenter.z, - newUp.x, newUp.y, newUp.z + newEye.x, + newEye.y, + newEye.z, + newCenter.x, + newCenter.y, + newCenter.z, + newUp.x, + newUp.y, + newUp.z ); } @@ -1732,7 +1805,8 @@ class Camera { this.defaultCenterX = 0; this.defaultCenterY = 0; this.defaultCenterZ = 0; - this.defaultCameraNear = this.defaultEyeZ * this._renderer.defaultNearScale(); + this.defaultCameraNear = + this.defaultEyeZ * this._renderer.defaultNearScale(); this.defaultCameraFar = this.defaultEyeZ * this._renderer.defaultFarScale(); } @@ -1935,9 +2009,15 @@ class Camera { // update camera this.camera( - this.eyeX, this.eyeY, this.eyeZ, - this.centerX, this.centerY, this.centerZ, - this.upX, this.upY, this.upZ + this.eyeX, + this.eyeY, + this.eyeZ, + this.centerX, + this.centerY, + this.centerZ, + this.upX, + this.upY, + this.upZ ); } @@ -2013,8 +2093,12 @@ class Camera { front.x + this.centerX, front.y + this.centerY, front.z + this.centerZ, - this.centerX, this.centerY, this.centerZ, - up.x, up.y, up.z + this.centerX, + this.centerY, + this.centerZ, + up.x, + up.y, + up.z ); } @@ -2025,9 +2109,9 @@ class Camera { _isActive() { return this === this._renderer.states.curCamera; } -}; +} -function camera(p5, fn){ +function camera(p5, fn) { //////////////////////////////////////////////////////////////////////////////// // p5.Prototype Methods //////////////////////////////////////////////////////////////////////////////// @@ -2397,7 +2481,6 @@ function camera(p5, fn){ return this._renderer.linePerspective(enable); }; - /** * Sets an orthographic projection for the current camera in a 3D sketch. * @@ -2855,17 +2938,17 @@ function camera(p5, fn){ */ p5.Camera = Camera; - Renderer3D.prototype.camera = function(...args) { + Renderer3D.prototype.camera = function (...args) { this.states.setValue('curCamera', this.states.curCamera.clone()); this.states.curCamera.camera(...args); }; - Renderer3D.prototype.perspective = function(...args) { + Renderer3D.prototype.perspective = function (...args) { this.states.setValue('curCamera', this.states.curCamera.clone()); this.states.curCamera.perspective(...args); }; - Renderer3D.prototype.linePerspective = function(enable) { + Renderer3D.prototype.linePerspective = function (enable) { if (enable !== undefined) { this.states.setValue('curCamera', this.states.curCamera.clone()); // Set the line perspective if enable is provided @@ -2876,17 +2959,17 @@ function camera(p5, fn){ } }; - Renderer3D.prototype.ortho = function(...args) { + Renderer3D.prototype.ortho = function (...args) { this.states.setValue('curCamera', this.states.curCamera.clone()); this.states.curCamera.ortho(...args); }; - Renderer3D.prototype.frustum = function(...args) { + Renderer3D.prototype.frustum = function (...args) { this.states.setValue('curCamera', this.states.curCamera.clone()); this.states.curCamera.frustum(...args); }; - Renderer3D.prototype.createCamera = function() { + Renderer3D.prototype.createCamera = function () { // compute default camera settings, then set a default camera const _cam = new Camera(this); _cam._computeCameraDefaultSettings(); @@ -2895,7 +2978,7 @@ function camera(p5, fn){ return _cam; }; - Renderer3D.prototype.setCamera = function(cam) { + Renderer3D.prototype.setCamera = function (cam) { this.states.setValue('curCamera', cam); // set the projection matrix (which is not normally updated each frame) @@ -3857,6 +3940,6 @@ function camera(p5, fn){ export default camera; export { Camera }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { camera(p5, p5.prototype); } diff --git a/src/webgl/p5.DataArray.js b/src/webgl/p5.DataArray.js index 9f5db5c33e..43e1068927 100644 --- a/src/webgl/p5.DataArray.js +++ b/src/webgl/p5.DataArray.js @@ -78,9 +78,9 @@ class DataArray { this.data = newData; } } -}; +} -function dataArray(p5, fn){ +function dataArray(p5, fn) { /** * An internal class to store data that will be sent to a p5.RenderBuffer. * Those need to eventually go into a Float32Array, so this class provides a @@ -108,6 +108,6 @@ function dataArray(p5, fn){ export default dataArray; export { DataArray }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { dataArray(p5, p5.prototype); } diff --git a/src/webgl/p5.Framebuffer.js b/src/webgl/p5.Framebuffer.js index c29e184545..9c63a1b419 100644 --- a/src/webgl/p5.Framebuffer.js +++ b/src/webgl/p5.Framebuffer.js @@ -68,11 +68,9 @@ class Framebuffer { this.pixels = []; this.format = settings.format || constants.UNSIGNED_BYTE; - this.channels = settings.channels || ( - this.renderer.defaultFramebufferAlpha() - ? RGBA - : RGB - ); + this.channels = + settings.channels || + (this.renderer.defaultFramebufferAlpha() ? RGBA : RGB); this.useDepth = settings.depth === undefined ? true : settings.depth; this.depthFormat = settings.depthFormat || constants.FLOAT; this.textureFiltering = settings.textureFiltering || constants.LINEAR; @@ -92,8 +90,11 @@ class Framebuffer { } this.density = settings.density || this.renderer._pixelDensity; if (settings.width && settings.height) { - const dimensions = - this.renderer._adjustDimensions(settings.width, settings.height, this.density); + const dimensions = this.renderer._adjustDimensions( + settings.width, + settings.height, + this.density + ); this.width = dimensions.adjustedWidth; this.height = dimensions.adjustedHeight; this._autoSized = false; @@ -113,9 +114,12 @@ class Framebuffer { this.renderer.validateFramebufferFormats(this); if (settings.stencil && !this.useDepth) { - console.warn('A stencil buffer can only be used if also using depth. Since the framebuffer has no depth buffer, the stencil buffer will be ignored.'); + console.warn( + 'A stencil buffer can only be used if also using depth. Since the framebuffer has no depth buffer, the stencil buffer will be ignored.' + ); } - this.useStencil = this.useDepth && + this.useStencil = + this.useDepth && (settings.stencil === undefined ? true : settings.stencil); // Let renderer create framebuffer resources with antialiasing support @@ -174,8 +178,11 @@ class Framebuffer { */ resize(width, height) { this._autoSized = false; - const dimensions = - this.renderer._adjustDimensions(width, height, this.density); + const dimensions = this.renderer._adjustDimensions( + width, + height, + this.density + ); width = dimensions.adjustedWidth; height = dimensions.adjustedHeight; this.width = width; @@ -377,11 +384,13 @@ class Framebuffer { this.depthFormat = constants.UNSIGNED_INT; } - if (![ - constants.UNSIGNED_BYTE, - constants.FLOAT, - constants.HALF_FLOAT - ].includes(this.format)) { + if ( + ![ + constants.UNSIGNED_BYTE, + constants.FLOAT, + constants.HALF_FLOAT + ].includes(this.format) + ) { console.warn( 'Unknown Framebuffer format. ' + 'Please use UNSIGNED_BYTE, FLOAT, or HALF_FLOAT. ' + @@ -389,10 +398,10 @@ class Framebuffer { ); this.format = constants.UNSIGNED_BYTE; } - if (this.useDepth && ![ - constants.UNSIGNED_INT, - constants.FLOAT - ].includes(this.depthFormat)) { + if ( + this.useDepth && + ![constants.UNSIGNED_INT, constants.FLOAT].includes(this.depthFormat) + ) { console.warn( 'Unknown Framebuffer depth format. ' + 'Please use UNSIGNED_INT or FLOAT. Defaulting to FLOAT.' @@ -458,29 +467,22 @@ class Framebuffer { if (this.useDepth) { this.depth = new FramebufferTexture(this, 'depthTexture'); const depthFilter = constants.NEAREST; - this.depthP5Texture = new Texture( - this.renderer, - this.depth, - { - minFilter: depthFilter, - magFilter: depthFilter - } - ); + this.depthP5Texture = new Texture(this.renderer, this.depth, { + minFilter: depthFilter, + magFilter: depthFilter + }); this.renderer.textures.set(this.depth, this.depthP5Texture); } this.color = new FramebufferTexture(this, 'colorTexture'); - const filter = this.textureFiltering === constants.LINEAR - ? constants.LINEAR - : constants.NEAREST; - this.colorP5Texture = new Texture( - this.renderer, - this.color, - { - minFilter: filter, - magFilter: filter - } - ); + const filter = + this.textureFiltering === constants.LINEAR + ? constants.LINEAR + : constants.NEAREST; + this.colorP5Texture = new Texture(this.renderer, this.color, { + minFilter: filter, + magFilter: filter + }); this.renderer.textures.set(this.color, this.colorP5Texture); } @@ -774,14 +776,21 @@ class Framebuffer { // this.renderer.setCamera(this.defaultCamera); this.renderer.states.setValue('curCamera', this.defaultCamera); // set the projection matrix (which is not normally updated each frame) - this.renderer.states.setValue('uPMatrix', this.renderer.states.uPMatrix.clone()); + this.renderer.states.setValue( + 'uPMatrix', + this.renderer.states.uPMatrix.clone() + ); this.renderer.states.uPMatrix.set(this.defaultCamera.projMatrix); - this.renderer.states.setValue('uViewMatrix', this.renderer.states.uViewMatrix.clone()); + this.renderer.states.setValue( + 'uViewMatrix', + this.renderer.states.uViewMatrix.clone() + ); this.renderer.states.uViewMatrix.set(this.defaultCamera.cameraMatrix); this.renderer.resetMatrix(); - this.renderer.states.uViewMatrix - .set(this.renderer.states.curCamera.cameraMatrix); + this.renderer.states.uViewMatrix.set( + this.renderer.states.curCamera.cameraMatrix + ); this.renderer.states.uModelMatrix.reset(); this.renderer._applyStencilTestIfClipping(); } @@ -892,7 +901,9 @@ class Framebuffer { const fbo = this.renderer.activeFramebuffers.pop(); if (fbo !== this) { - throw new Error("It looks like you've called end() while another Framebuffer is active."); + throw new Error( + "It looks like you've called end() while another Framebuffer is active." + ); } this._beforeEnd(); if (this.prevFramebuffer) { @@ -1076,7 +1087,11 @@ class Framebuffer { y = constrain(y, 0, this.height - 1); } - return this.renderer.readFramebufferPixel(this, x * this.density, y * this.density); + return this.renderer.readFramebufferPixel( + this, + x * this.density, + y * this.density + ); } x = constrain(x, 0, this.width - 1); @@ -1137,7 +1152,7 @@ class Framebuffer { } } -function framebuffer(p5, fn){ +function framebuffer(p5, fn) { /** * A p5.Camera attached to a * p5.Framebuffer. @@ -1405,6 +1420,6 @@ function framebuffer(p5, fn){ export default framebuffer; export { FramebufferTexture, FramebufferCamera, Framebuffer }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { framebuffer(p5, p5.prototype); } diff --git a/src/webgl/p5.Geometry.js b/src/webgl/p5.Geometry.js index 67658cfb49..d89b49ae35 100644 --- a/src/webgl/p5.Geometry.js +++ b/src/webgl/p5.Geometry.js @@ -18,7 +18,6 @@ class Geometry { this.boundingBoxCache = null; - //an array containing every vertex for stroke drawing this.lineVertices = new DataArray(); @@ -168,9 +167,15 @@ class Geometry { } let minVertex = new Vector( - Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); + Number.MAX_VALUE, + Number.MAX_VALUE, + Number.MAX_VALUE + ); let maxVertex = new Vector( - Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE); + Number.MIN_VALUE, + Number.MIN_VALUE, + Number.MIN_VALUE + ); for (let i = 0; i < this.vertices.length; i++) { let vertex = this.vertices[i]; @@ -183,10 +188,16 @@ class Geometry { maxVertex.z = Math.max(maxVertex.z, vertex.z); } // Calculate size and offset properties - let size = new Vector(maxVertex.x - minVertex.x, - maxVertex.y - minVertex.y, maxVertex.z - minVertex.z); - let offset = new Vector((minVertex.x + maxVertex.x) / 2, - (minVertex.y + maxVertex.y) / 2, (minVertex.z + maxVertex.z) / 2); + let size = new Vector( + maxVertex.x - minVertex.x, + maxVertex.y - minVertex.y, + maxVertex.z - minVertex.z + ); + let offset = new Vector( + (minVertex.x + maxVertex.x) / 2, + (minVertex.y + maxVertex.y) / 2, + (minVertex.z + maxVertex.z) / 2 + ); // Cache the result for future access this.boundingBoxCache = { @@ -219,7 +230,7 @@ class Geometry { this.vertexNormals.length = 0; this.uvs.length = 0; - for (const propName in this.userVertexProperties){ + for (const propName in this.userVertexProperties) { this.userVertexProperties[propName].delete(); } this.userVertexProperties = {}; @@ -357,8 +368,7 @@ class Geometry { * } */ saveObj(fileName = 'model.obj') { - let objStr= ''; - + let objStr = ''; // Vertices this.vertices.forEach(v => { @@ -377,7 +387,6 @@ class Geometry { this.vertexNormals.forEach(n => { objStr += `vn ${n.x} ${n.y} ${n.z}\n`; }); - } // Faces, obj vertex indices begin with 1 and not 0 // texture coordinate (uvs) and vertexNormal indices @@ -385,7 +394,7 @@ class Geometry { // ex 1/1/1 or 2//2 for vertices without uvs this.faces.forEach(face => { let faceStr = 'f'; - face.forEach(index =>{ + face.forEach(index => { faceStr += ' '; faceStr += index + 1; if (this.vertexNormals.length > 0 || this.uvs.length > 0) { @@ -403,8 +412,7 @@ class Geometry { }); const blob = new Blob([objStr], { type: 'text/plain' }); - downloadFile(blob, fileName , 'obj'); - + downloadFile(blob, fileName, 'obj'); } /** @@ -463,7 +471,7 @@ class Geometry { * model(myModel); * } */ - saveStl(fileName = 'model.stl', { binary = false } = {}){ + saveStl(fileName = 'model.stl', { binary = false } = {}) { let modelOutput; let name = fileName.substring(0, fileName.lastIndexOf('.')); let faceNormals = []; @@ -1374,30 +1382,30 @@ class Geometry { const isPoint = currEdge[0] === currEdge[1]; const begin = this.vertices[currEdge[0]]; const end = this.vertices[currEdge[1]]; - const prevColor = (this.vertexStrokeColors.length > 0 && prevEdge) - ? this.vertexStrokeColors.slice( - prevEdge[1] * 4, - (prevEdge[1] + 1) * 4 - ) - : [0, 0, 0, 0]; - const fromColor = this.vertexStrokeColors.length > 0 - ? this.vertexStrokeColors.slice( - currEdge[0] * 4, - (currEdge[0] + 1) * 4 - ) - : [0, 0, 0, 0]; - const toColor = this.vertexStrokeColors.length > 0 - ? this.vertexStrokeColors.slice( - currEdge[1] * 4, - (currEdge[1] + 1) * 4 - ) - : [0, 0, 0, 0]; + const prevColor = + this.vertexStrokeColors.length > 0 && prevEdge + ? this.vertexStrokeColors.slice( + prevEdge[1] * 4, + (prevEdge[1] + 1) * 4 + ) + : [0, 0, 0, 0]; + const fromColor = + this.vertexStrokeColors.length > 0 + ? this.vertexStrokeColors.slice( + currEdge[0] * 4, + (currEdge[0] + 1) * 4 + ) + : [0, 0, 0, 0]; + const toColor = + this.vertexStrokeColors.length > 0 + ? this.vertexStrokeColors.slice( + currEdge[1] * 4, + (currEdge[1] + 1) * 4 + ) + : [0, 0, 0, 0]; const dir = isPoint ? new Vector(0, 1, 0) - : end - .copy() - .sub(begin) - .normalize(); + : end.copy().sub(begin).normalize(); const dirOK = dir.magSq() > 0; if (dirOK) { this._addSegment(begin, end, fromColor, toColor, dir); @@ -1425,12 +1433,7 @@ class Geometry { if (dirOK && !connected.has(currEdge[0])) { const existingCap = potentialCaps.get(currEdge[0]); if (existingCap) { - this._addJoin( - begin, - existingCap.dir, - dir, - fromColor - ); + this._addJoin(begin, existingCap.dir, dir, fromColor); potentialCaps.delete(currEdge[0]); connected.add(currEdge[0]); } else { @@ -1467,12 +1470,7 @@ class Geometry { if (i === this.edges.length - 1 && !connected.has(currEdge[1])) { const existingCap = potentialCaps.get(currEdge[1]); if (existingCap) { - this._addJoin( - end, - dir, - existingCap.dir.copy().mult(-1), - toColor - ); + this._addJoin(end, dir, existingCap.dir.copy().mult(-1), toColor); potentialCaps.delete(currEdge[1]); connected.add(currEdge[1]); } else { @@ -1511,13 +1509,7 @@ class Geometry { * @private * @chainable */ - _addSegment( - begin, - end, - fromColor, - toColor, - dir - ) { + _addSegment(begin, end, fromColor, toColor, dir) { const a = begin.array(); const b = end.array(); const dirArr = dir.array(); @@ -1594,12 +1586,7 @@ class Geometry { * @private * @chainable */ - _addJoin( - point, - fromTangent, - toTangent, - color - ) { + _addJoin(point, fromTangent, toTangent, color) { const ptArray = point.array(); const tanInArray = fromTangent.array(); const tanOutArray = toTangent.array(); @@ -1789,32 +1776,32 @@ class Geometry { * @param {Number|Number[]} data the data tied to the vertex property. * @param {Number} [size] optional size of each unit of data. */ - vertexProperty(propertyName, data, size){ + vertexProperty(propertyName, data, size) { let prop; - if (!this.userVertexProperties[propertyName]){ + if (!this.userVertexProperties[propertyName]) { prop = this.userVertexProperties[propertyName] = this._userVertexPropertyHelper(propertyName, data, size); } prop = this.userVertexProperties[propertyName]; - if (size){ + if (size) { prop.pushDirect(data); - } else{ + } else { prop.setCurrentData(data); prop.pushCurrentData(); } } - _userVertexPropertyHelper(propertyName, data, size){ + _userVertexPropertyHelper(propertyName, data, size) { const geometryInstance = this; - const prop = this.userVertexProperties[propertyName] = { + const prop = (this.userVertexProperties[propertyName] = { name: propertyName, dataSize: size ? size : data.length ? data.length : 1, geometry: geometryInstance, // Getters - getName(){ + getName() { return this.name; }, - getCurrentData(){ + getCurrentData() { if (this.currentData === undefined) { this.currentData = new Array(this.getDataSize()).fill(0); } @@ -1844,18 +1831,18 @@ class Geometry { this.currentData = data; }, // Utilities - pushCurrentData(){ + pushCurrentData() { const data = this.getCurrentData(); this.pushDirect(data); }, pushDirect(data) { - if (data.length){ + if (data.length) { this.getSrcArray().push(...data); - } else{ + } else { this.getSrcArray().push(data); } }, - resetSrcArray(){ + resetSrcArray() { this.geometry[this.getSrcName()] = []; }, delete() { @@ -1863,11 +1850,11 @@ class Geometry { delete this.geometry[srcName]; delete this; } - }; + }); this[prop.getSrcName()] = []; return this.userVertexProperties[propertyName]; } -}; +} /** * Keeps track of how many custom geometry objects have been made so that each @@ -1875,7 +1862,7 @@ class Geometry { */ Geometry.nextId = 0; -function geometry(p5, fn){ +function geometry(p5, fn) { /** * A class to describe a 3D shape. * @@ -2476,6 +2463,6 @@ function geometry(p5, fn){ export default geometry; export { Geometry }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { geometry(p5, p5.prototype); } diff --git a/src/webgl/p5.Quat.js b/src/webgl/p5.Quat.js index d9204aec32..4391d3d8d5 100644 --- a/src/webgl/p5.Quat.js +++ b/src/webgl/p5.Quat.js @@ -23,8 +23,8 @@ class Quat { * @chainable */ static fromAxisAngle(angle, x, y, z) { - const w = Math.cos(angle/2); - const vec = new Vector(x, y, z).normalize().mult(Math.sin(angle/2)); + const w = Math.cos(angle / 2); + const vec = new Vector(x, y, z).normalize().mult(Math.sin(angle / 2)); return new Quat(w, vec.x, vec.y, vec.z); } @@ -39,14 +39,24 @@ class Quat { * @chainable */ multiply(quat) { - return new Quat( - this.w * quat.w - this.vec.x * quat.vec.x - this.vec.y * quat.vec.y - this.vec.z * quat.vec.z, - this.w * quat.vec.x + this.vec.x * quat.w + this.vec.y * quat.vec.z - this.vec.z * quat.vec.y, - this.w * quat.vec.y - this.vec.x * quat.vec.z + this.vec.y * quat.w + this.vec.z * quat.vec.x, - this.w * quat.vec.z + this.vec.x * quat.vec.y - this.vec.y * quat.vec.x + this.vec.z * quat.w + this.w * quat.w - + this.vec.x * quat.vec.x - + this.vec.y * quat.vec.y - + this.vec.z * quat.vec.z, + this.w * quat.vec.x + + this.vec.x * quat.w + + this.vec.y * quat.vec.z - + this.vec.z * quat.vec.y, + this.w * quat.vec.y - + this.vec.x * quat.vec.z + + this.vec.y * quat.w + + this.vec.z * quat.vec.x, + this.w * quat.vec.z + + this.vec.x * quat.vec.y - + this.vec.y * quat.vec.x + + this.vec.z * quat.w ); - } /** @@ -59,9 +69,9 @@ class Quat { * @param {p5.Vector} [p] vector to rotate on the axis quaternion */ rotateVector(p) { - return Vector.mult( p, this.w*this.w - this.vec.dot(this.vec) ) - .add( Vector.mult( this.vec, 2 * p.dot(this.vec) ) ) - .add( Vector.mult( this.vec, 2 * this.w ).cross( p ) ) + return Vector.mult(p, this.w * this.w - this.vec.dot(this.vec)) + .add(Vector.mult(this.vec, 2 * p.dot(this.vec))) + .add(Vector.mult(this.vec, 2 * this.w).cross(p)) .clampToZero(); } @@ -75,12 +85,14 @@ class Quat { * @chainable */ rotateBy(axesQuat) { - return axesQuat.multiply(this).multiply(axesQuat.conjugate()). - vec.clampToZero(); + return axesQuat + .multiply(this) + .multiply(axesQuat.conjugate()) + .vec.clampToZero(); } } -function quat(p5, fn){ +function quat(p5, fn) { /** * A class to describe a Quaternion * for vector rotations in the p5js webgl renderer. @@ -100,6 +112,6 @@ function quat(p5, fn){ export default quat; export { Quat }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { quat(p5, p5.prototype); } diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index f2e61d6e60..d81b86d882 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -20,24 +20,24 @@ import { glslBackend } from './strands_glslBackend'; import { TypeInfoFromGLSLName } from '../strands/ir_types.js'; import { getShaderHookTypes } from './shaderHookUtils'; -import filterBaseVert from "./shaders/filters/base.vert"; -import lightingShader from "./shaders/lighting.glsl"; -import webgl2CompatibilityShader from "./shaders/webgl2Compatibility.glsl"; -import normalVert from "./shaders/normal.vert"; -import normalFrag from "./shaders/normal.frag"; -import basicFrag from "./shaders/basic.frag"; -import lightVert from "./shaders/light.vert"; -import lightTextureFrag from "./shaders/light_texture.frag"; -import phongVert from "./shaders/phong.vert"; -import phongFrag from "./shaders/phong.frag"; -import fontVert from "./shaders/font.vert"; -import fontFrag from "./shaders/font.frag"; -import lineVert from "./shaders/line.vert"; -import lineFrag from "./shaders/line.frag"; -import imageLightVert from "./shaders/imageLight.vert"; -import imageLightDiffusedFrag from "./shaders/imageLightDiffused.frag"; -import imageLightSpecularFrag from "./shaders/imageLightSpecular.frag"; -import filterBaseFrag from "./shaders/filters/base.frag"; +import filterBaseVert from './shaders/filters/base.vert'; +import lightingShader from './shaders/lighting.glsl'; +import webgl2CompatibilityShader from './shaders/webgl2Compatibility.glsl'; +import normalVert from './shaders/normal.vert'; +import normalFrag from './shaders/normal.frag'; +import basicFrag from './shaders/basic.frag'; +import lightVert from './shaders/light.vert'; +import lightTextureFrag from './shaders/light_texture.frag'; +import phongVert from './shaders/phong.vert'; +import phongFrag from './shaders/phong.frag'; +import fontVert from './shaders/font.vert'; +import fontFrag from './shaders/font.frag'; +import lineVert from './shaders/line.vert'; +import lineFrag from './shaders/line.frag'; +import imageLightVert from './shaders/imageLight.vert'; +import imageLightDiffusedFrag from './shaders/imageLightDiffused.frag'; +import imageLightSpecularFrag from './shaders/imageLightSpecular.frag'; +import filterBaseFrag from './shaders/filters/base.frag'; const { lineDefs } = getStrokeDefs((n, v) => `#define ${n} ${v}\n`); @@ -57,7 +57,7 @@ const defaultShaders = { imageLightDiffusedFrag, imageLightSpecularFrag, filterBaseVert, - filterBaseFrag, + filterBaseFrag }; for (const key in defaultShaders) { defaultShaders[key] = webgl2CompatibilityShader + defaultShaders[key]; @@ -78,7 +78,7 @@ class RendererGL extends Renderer3D { if (this.webglVersion === constants.WEBGL2) { this.blendExt = this.GL; } else { - this.blendExt = this.GL.getExtension("EXT_blend_minmax"); + this.blendExt = this.GL.getExtension('EXT_blend_minmax'); } this._userEnabledStencil = false; @@ -87,7 +87,7 @@ class RendererGL extends Renderer3D { this._internalDisable = this.drawingContext.disable; // Override WebGL enable function - this.drawingContext.enable = (key) => { + this.drawingContext.enable = key => { if (key === this.drawingContext.STENCIL_TEST) { if (!this._clipping) { this._userEnabledStencil = true; @@ -97,9 +97,9 @@ class RendererGL extends Renderer3D { }; // Override WebGL disable function - this.drawingContext.disable = (key) => { + this.drawingContext.disable = key => { if (key === this.drawingContext.STENCIL_TEST) { - this._userEnabledStencil = false; + this._userEnabledStencil = false; } return this._internalDisable.call(this.drawingContext, key); }; @@ -125,7 +125,7 @@ class RendererGL extends Renderer3D { * @param {Number[]} color [description] * @return {Number[]} Normalized numbers array */ - _applyBlendMode () { + _applyBlendMode() { if (this._cachedBlendMode === this.states.curBlendMode) { return; } @@ -237,23 +237,23 @@ class RendererGL extends Renderer3D { if (!glBuffers) return; - if (this._curShader.shaderType === 'stroke'){ + if (this._curShader.shaderType === 'stroke') { if (count === 1) { gl.drawArrays(gl.TRIANGLES, 0, geometry.lineVertices.length / 3); - } else { - try { + } else { + try { gl.drawArraysInstanced( - gl.TRIANGLES, + gl.TRIANGLES, 0, geometry.lineVertices.length / 3, count ); } catch (e) { console.log( - "🌸 p5.js says: Instancing is only supported in WebGL2 mode" + '🌸 p5.js says: Instancing is only supported in WebGL2 mode' ); } - } + } } else if (this._curShader.shaderType === 'text') { // Text rendering uses a fixed quad geometry with 6 indices this._bindBuffer(glBuffers.indexBuffer, gl.ELEMENT_ARRAY_BUFFER); @@ -267,9 +267,9 @@ class RendererGL extends Renderer3D { this._pInst.webglVersion !== constants.WEBGL2 && glBuffers.indexBufferType === gl.UNSIGNED_INT ) { - if (!gl.getExtension("OES_element_index_uint")) { + if (!gl.getExtension('OES_element_index_uint')) { throw new Error( - "Unable to render a 3d model with > 65535 triangles. Your web browser does not support the WebGL Extension OES_element_index_uint." + 'Unable to render a 3d model with > 65535 triangles. Your web browser does not support the WebGL Extension OES_element_index_uint.' ); } } @@ -292,7 +292,7 @@ class RendererGL extends Renderer3D { ); } catch (e) { console.log( - "🌸 p5.js says: Instancing is only supported in WebGL2 mode" + '🌸 p5.js says: Instancing is only supported in WebGL2 mode' ); } } @@ -312,7 +312,7 @@ class RendererGL extends Renderer3D { gl.drawArraysInstanced(glMode, 0, geometry.vertices.length, count); } catch (e) { console.log( - "🌸 p5.js says: Instancing is only supported in WebGL2 mode" + '🌸 p5.js says: Instancing is only supported in WebGL2 mode' ); } } @@ -336,7 +336,7 @@ class RendererGL extends Renderer3D { _setAttributeDefaults(pInst) { // See issue #3850, safer to enable AA in Safari - const applyAA = navigator.userAgent.toLowerCase().includes("safari"); + const applyAA = navigator.userAgent.toLowerCase().includes('safari'); const defaults = { alpha: true, depth: true, @@ -345,7 +345,7 @@ class RendererGL extends Renderer3D { premultipliedAlpha: true, preserveDrawingBuffer: true, perPixelLighting: true, - version: 2, + version: 2 }; if (pInst._glAttributes === null) { pInst._glAttributes = defaults; @@ -356,15 +356,15 @@ class RendererGL extends Renderer3D { } _setAttributes(key, value) { - if (typeof this._pInst._glAttributes === "undefined") { + if (typeof this._pInst._glAttributes === 'undefined') { console.log( - "You are trying to use setAttributes on a p5.Graphics object " + - "that does not use a WEBGL renderer." + 'You are trying to use setAttributes on a p5.Graphics object ' + + 'that does not use a WEBGL renderer.' ); return; } let unchanged = true; - if (typeof value !== "undefined") { + if (typeof value !== 'undefined') { //first time modifying the attributes if (this._pInst._glAttributes === null) { this._pInst._glAttributes = {}; @@ -388,7 +388,8 @@ class RendererGL extends Renderer3D { if (!this._pInst._setupDone) { if (this.geometryBufferCache.numCached() > 0) { - p5.FES.log`Sorry, Could not set the attributes, you need to call setAttributes() before calling the other drawing methods in setup()`(); + p5.FES + .log`Sorry, Could not set the attributes, you need to call setAttributes() before calling the other drawing methods in setup()`(); return; } } @@ -404,7 +405,7 @@ class RendererGL extends Renderer3D { if (this._pInst._glAttributes?.version !== 1) { // Unless WebGL1 is explicitly asked for, try to create a WebGL2 context this.drawingContext = this.canvas.getContext( - "webgl2", + 'webgl2', this._pInst._glAttributes ); } @@ -418,11 +419,11 @@ class RendererGL extends Renderer3D { // disabled via `setAttributes({ version: 1 })` or because the device // doesn't support it), fall back to a WebGL1 context this.drawingContext = - this.canvas.getContext("webgl", this._pInst._glAttributes) || - this.canvas.getContext("experimental-webgl", this._pInst._glAttributes); + this.canvas.getContext('webgl', this._pInst._glAttributes) || + this.canvas.getContext('experimental-webgl', this._pInst._glAttributes); } if (this.drawingContext === null) { - throw new Error("Error creating webgl context"); + throw new Error('Error creating webgl context'); } else { const gl = this.drawingContext; gl.enable(gl.DEPTH_TEST); @@ -451,15 +452,13 @@ class RendererGL extends Renderer3D { } let maxTextureSize = this._maxTextureSize; - let maxAllowedPixelDimensions = Math.floor( - maxTextureSize / density - ); + let maxAllowedPixelDimensions = Math.floor(maxTextureSize / density); let adjustedWidth = Math.min(width, maxAllowedPixelDimensions); let adjustedHeight = Math.min(height, maxAllowedPixelDimensions); if (adjustedWidth !== width || adjustedHeight !== height) { console.warn( - "Warning: The requested width/height exceeds hardware limits. " + + 'Warning: The requested width/height exceeds hardware limits. ' + `Adjusting dimensions to width: ${adjustedWidth}, height: ${adjustedHeight}.` ); } @@ -542,8 +541,8 @@ class RendererGL extends Renderer3D { //@todo_FES if (this._pInst._glAttributes.preserveDrawingBuffer !== true) { console.log( - "loadPixels only works in WebGL when preserveDrawingBuffer " + - "is true." + 'loadPixels only works in WebGL when preserveDrawingBuffer ' + + 'is true.' ); return; } @@ -572,7 +571,7 @@ class RendererGL extends Renderer3D { this.push(); this.resetMatrix(); this.clear(); - this.states.setValue("imageMode", constants.CORNER); + this.states.setValue('imageMode', constants.CORNER); this.image( fbo, 0, @@ -611,7 +610,7 @@ class RendererGL extends Renderer3D { _updateViewport() { this._origViewport = { width: this.GL.drawingBufferWidth, - height: this.GL.drawingBufferHeight, + height: this.GL.drawingBufferHeight }; this.viewport(this._origViewport.width, this._origViewport.height); } @@ -681,7 +680,6 @@ class RendererGL extends Renderer3D { } } - ////////////////////////////////////////////// // SHADER ////////////////////////////////////////////// @@ -695,7 +693,7 @@ class RendererGL extends Renderer3D { baseMaterialShader() { if (!this._pInst._glAttributes.perPixelLighting) { throw new Error( - "The material shader does not support hooks without perPixelLighting. Try turning it back on." + 'The material shader does not support hooks without perPixelLighting. Try turning it back on.' ); } return super.baseMaterialShader(); @@ -706,22 +704,22 @@ class RendererGL extends Renderer3D { if (this._pInst._glAttributes.perPixelLighting) { this._defaultLightShader = new Shader( this, - this._webGL2CompatibilityPrefix("vert", "highp") + + this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.phongVert, - this._webGL2CompatibilityPrefix("frag", "highp") + + this._webGL2CompatibilityPrefix('frag', 'highp') + defaultShaders.phongFrag, { vertex: { - "void beforeVertex": "() {}", - "Vertex getObjectInputs": "(Vertex inputs) { return inputs; }", - "Vertex getWorldInputs": "(Vertex inputs) { return inputs; }", - "Vertex getCameraInputs": "(Vertex inputs) { return inputs; }", - "void afterVertex": "() {}", + 'void beforeVertex': '() {}', + 'Vertex getObjectInputs': '(Vertex inputs) { return inputs; }', + 'Vertex getWorldInputs': '(Vertex inputs) { return inputs; }', + 'Vertex getCameraInputs': '(Vertex inputs) { return inputs; }', + 'void afterVertex': '() {}' }, fragment: { - "void beforeFragment": "() {}", - "Inputs getPixelInputs": "(Inputs inputs) { return inputs; }", - "vec4 combineColors": `(ColorComponents components) { + 'void beforeFragment': '() {}', + 'Inputs getPixelInputs': '(Inputs inputs) { return inputs; }', + 'vec4 combineColors': `(ColorComponents components) { vec4 color = vec4(0.); color.rgb += components.diffuse * components.baseColor; color.rgb += components.ambient * components.ambientColor; @@ -730,17 +728,18 @@ class RendererGL extends Renderer3D { color.a = components.opacity; return color; }`, - "vec4 getFinalColor": "(vec4 color, vec2 texCoord) { return color; }", - "void afterFragment": "() {}", - }, + 'vec4 getFinalColor': + '(vec4 color, vec2 texCoord) { return color; }', + 'void afterFragment': '() {}' + } } ); } else { this._defaultLightShader = new Shader( this, - this._webGL2CompatibilityPrefix("vert", "highp") + + this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.lightVert, - this._webGL2CompatibilityPrefix("frag", "highp") + + this._webGL2CompatibilityPrefix('frag', 'highp') + defaultShaders.lightTextureFrag ); } @@ -753,23 +752,24 @@ class RendererGL extends Renderer3D { if (!this._defaultNormalShader) { this._defaultNormalShader = new Shader( this, - this._webGL2CompatibilityPrefix("vert", "highp") + + this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.normalVert, - this._webGL2CompatibilityPrefix("frag", "highp") + + this._webGL2CompatibilityPrefix('frag', 'highp') + defaultShaders.normalFrag, { vertex: { - "void beforeVertex": "() {}", - "Vertex getObjectInputs": "(Vertex inputs) { return inputs; }", - "Vertex getWorldInputs": "(Vertex inputs) { return inputs; }", - "Vertex getCameraInputs": "(Vertex inputs) { return inputs; }", - "void afterVertex": "() {}", + 'void beforeVertex': '() {}', + 'Vertex getObjectInputs': '(Vertex inputs) { return inputs; }', + 'Vertex getWorldInputs': '(Vertex inputs) { return inputs; }', + 'Vertex getCameraInputs': '(Vertex inputs) { return inputs; }', + 'void afterVertex': '() {}' }, fragment: { - "void beforeFragment": "() {}", - "vec4 getFinalColor": "(vec4 color, vec2 texCoord) { return color; }", - "void afterFragment": "() {}", - }, + 'void beforeFragment': '() {}', + 'vec4 getFinalColor': + '(vec4 color, vec2 texCoord) { return color; }', + 'void afterFragment': '() {}' + } } ); } @@ -781,23 +781,24 @@ class RendererGL extends Renderer3D { if (!this._defaultColorShader) { this._defaultColorShader = new Shader( this, - this._webGL2CompatibilityPrefix("vert", "highp") + + this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.normalVert, - this._webGL2CompatibilityPrefix("frag", "highp") + + this._webGL2CompatibilityPrefix('frag', 'highp') + defaultShaders.basicFrag, { vertex: { - "void beforeVertex": "() {}", - "Vertex getObjectInputs": "(Vertex inputs) { return inputs; }", - "Vertex getWorldInputs": "(Vertex inputs) { return inputs; }", - "Vertex getCameraInputs": "(Vertex inputs) { return inputs; }", - "void afterVertex": "() {}", + 'void beforeVertex': '() {}', + 'Vertex getObjectInputs': '(Vertex inputs) { return inputs; }', + 'Vertex getWorldInputs': '(Vertex inputs) { return inputs; }', + 'Vertex getCameraInputs': '(Vertex inputs) { return inputs; }', + 'void afterVertex': '() {}' }, fragment: { - "void beforeFragment": "() {}", - "vec4 getFinalColor": "(vec4 color, vec2 texCoord) { return color; }", - "void afterFragment": "() {}", - }, + 'void beforeFragment': '() {}', + 'vec4 getFinalColor': + '(vec4 color, vec2 texCoord) { return color; }', + 'void afterFragment': '() {}' + } } ); } @@ -809,28 +810,29 @@ class RendererGL extends Renderer3D { if (!this._defaultLineShader) { this._defaultLineShader = new Shader( this, - this._webGL2CompatibilityPrefix("vert", "highp") + + this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.lineVert, - this._webGL2CompatibilityPrefix("frag", "highp") + + this._webGL2CompatibilityPrefix('frag', 'highp') + defaultShaders.lineFrag, { vertex: { - "void beforeVertex": "() {}", - "StrokeVertex getObjectInputs": - "(StrokeVertex inputs) { return inputs; }", - "StrokeVertex getWorldInputs": - "(StrokeVertex inputs) { return inputs; }", - "StrokeVertex getCameraInputs": - "(StrokeVertex inputs) { return inputs; }", - "void afterVertex": "() {}", + 'void beforeVertex': '() {}', + 'StrokeVertex getObjectInputs': + '(StrokeVertex inputs) { return inputs; }', + 'StrokeVertex getWorldInputs': + '(StrokeVertex inputs) { return inputs; }', + 'StrokeVertex getCameraInputs': + '(StrokeVertex inputs) { return inputs; }', + 'void afterVertex': '() {}' }, fragment: { - "void beforeFragment": "() {}", - "Inputs getPixelInputs": "(Inputs inputs) { return inputs; }", - "vec4 getFinalColor": "(vec4 color, vec2 texCoord) { return color; }", - "bool shouldDiscard": "(bool outside) { return outside; }", - "void afterFragment": "() {}", - }, + 'void beforeFragment': '() {}', + 'Inputs getPixelInputs': '(Inputs inputs) { return inputs; }', + 'vec4 getFinalColor': + '(vec4 color, vec2 texCoord) { return color; }', + 'bool shouldDiscard': '(bool outside) { return outside; }', + 'void afterFragment': '() {}' + } } ); } @@ -841,13 +843,13 @@ class RendererGL extends Renderer3D { _getFontShader() { if (!this._defaultFontShader) { if (this.webglVersion === constants.WEBGL) { - this.GL.getExtension("OES_standard_derivatives"); + this.GL.getExtension('OES_standard_derivatives'); } this._defaultFontShader = new Shader( this, - this._webGL2CompatibilityPrefix("vert", "highp") + + this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.fontVert, - this._webGL2CompatibilityPrefix("frag", "highp") + + this._webGL2CompatibilityPrefix('frag', 'highp') + defaultShaders.fontFrag ); } @@ -858,20 +860,20 @@ class RendererGL extends Renderer3D { if (!this._baseFilterShader) { this._baseFilterShader = new Shader( this, - this._webGL2CompatibilityPrefix("vert", "highp") + + this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.filterBaseVert, - this._webGL2CompatibilityPrefix("frag", "highp") + + this._webGL2CompatibilityPrefix('frag', 'highp') + defaultShaders.filterBaseFrag, { vertex: {}, fragment: { - "vec4 getColor": `(FilterInputs inputs, in sampler2D canvasContent) { + 'vec4 getColor': `(FilterInputs inputs, in sampler2D canvasContent) { return getTexture(canvasContent, inputs.texCoord); - }`, + }` }, hookAliases: { - 'getColor': ['filterColor'], - }, + getColor: ['filterColor'] + } } ); } @@ -879,14 +881,14 @@ class RendererGL extends Renderer3D { } _webGL2CompatibilityPrefix(shaderType, floatPrecision) { - let code = ""; + let code = ''; if (this.webglVersion === constants.WEBGL2) { - code += "#version 300 es\n#define WEBGL2\n"; + code += '#version 300 es\n#define WEBGL2\n'; } - if (shaderType === "vert") { - code += "#define VERTEX_SHADER\n"; - } else if (shaderType === "frag") { - code += "#define FRAGMENT_SHADER\n"; + if (shaderType === 'vert') { + code += '#define VERTEX_SHADER\n'; + } else if (shaderType === 'frag') { + code += '#define FRAGMENT_SHADER\n'; } if (floatPrecision) { code += `precision ${floatPrecision} float;\n`; @@ -901,24 +903,23 @@ class RendererGL extends Renderer3D { if (type === 'diffused') { return new Shader( this, - this._webGL2CompatibilityPrefix("vert", "highp") + + this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.imageLightVert, - this._webGL2CompatibilityPrefix("frag", "highp") + + this._webGL2CompatibilityPrefix('frag', 'highp') + defaultShaders.imageLightDiffusedFrag ); } else if (type === 'specular') { return new Shader( this, - this._webGL2CompatibilityPrefix("vert", "highp") + + this._webGL2CompatibilityPrefix('vert', 'highp') + defaultShaders.imageLightVert, - this._webGL2CompatibilityPrefix("frag", "highp") + + this._webGL2CompatibilityPrefix('frag', 'highp') + defaultShaders.imageLightSpecularFrag ); } throw new Error(`Unknown imageLight shader type: ${type}`); } - /* * WebGL-specific implementation of mipmap texture creation */ @@ -937,7 +938,9 @@ class RendererGL extends Renderer3D { * Accumulate ImageData from framebuffer for WebGL */ _accumulateMipLevel(framebuffer, mipmapData, mipLevel, width, height) { - const imageData = framebuffer.get().drawingContext.getImageData(0, 0, width, height); + const imageData = framebuffer + .get() + .drawingContext.getImageData(0, 0, width, height); mipmapData.levels.push(imageData); } @@ -947,7 +950,7 @@ class RendererGL extends Renderer3D { _finalizeMipmapTexture(mipmapData) { return new MipmapTexture(this, mipmapData.levels, { minFilter: constants.LINEAR_MIPMAP, - magFilter: constants.LINEAR, + magFilter: constants.LINEAR }); } @@ -974,7 +977,11 @@ class RendererGL extends Renderer3D { // Set mipmap-appropriate filtering gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_MIN_FILTER, + gl.LINEAR_MIPMAP_LINEAR + ); gl.bindTexture(gl.TEXTURE_2D, null); @@ -1075,7 +1082,8 @@ class RendererGL extends Renderer3D { // If we're using a Uint32Array for our indexBuffer we will need to pass a // different enum value to WebGL draw triangles. This happens in // the _drawElements function. - buffers.indexBufferType = indexType === Uint32Array ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT; + buffers.indexBufferType = + indexType === Uint32Array ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT; } else if (buffers.indexBuffer) { // the index buffer is unused, remove it gl.deleteBuffer(buffers.indexBuffer); @@ -1111,18 +1119,22 @@ class RendererGL extends Renderer3D { gl.shaderSource(vertShader, shader.vertSrc()); gl.compileShader(vertShader); if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) { - throw new Error(`Yikes! An error occurred compiling the vertex shader: ${ - gl.getShaderInfoLog(vertShader) - } in:\n\n${shader.vertSrc()}`); + throw new Error( + `Yikes! An error occurred compiling the vertex shader: ${gl.getShaderInfoLog( + vertShader + )} in:\n\n${shader.vertSrc()}` + ); } const fragShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragShader, shader.fragSrc()); gl.compileShader(fragShader); if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) { - throw new Error(`Darn! An error occurred compiling the fragment shader: ${ - gl.getShaderInfoLog(fragShader) - }`); + throw new Error( + `Darn! An error occurred compiling the fragment shader: ${gl.getShaderInfoLog( + fragShader + )}` + ); } const program = gl.createProgram(); @@ -1157,7 +1169,7 @@ class RendererGL extends Renderer3D { shader, uniform, data, - (tex) => this.getTexture(tex), + tex => this.getTexture(tex), this.GL ); } @@ -1196,8 +1208,17 @@ class RendererGL extends Renderer3D { const gl = this.GL; const tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, - gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + width, + height, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null + ); // TODO use format and data type return { texture: tex, glFormat: gl.RGBA, glDataType: gl.UNSIGNED_BYTE }; } @@ -1212,7 +1233,12 @@ class RendererGL extends Renderer3D { gl.texImage2D(gl.TEXTURE_2D, 0, glFormat, glFormat, glDataType, source); } - uploadTextureFromData({ texture, glFormat, glDataType }, data, width, height) { + uploadTextureFromData( + { texture, glFormat, glDataType }, + data, + width, + height + ) { const gl = this.GL; gl.texImage2D( gl.TEXTURE_2D, @@ -1247,7 +1273,6 @@ class RendererGL extends Renderer3D { this.GL.deleteTexture(texture); } - /** * @private blends colors according to color components. * If alpha value is less than 1, or non-standard blendMode @@ -1362,11 +1387,13 @@ class RendererGL extends Renderer3D { framebuffer.depthFormat = constants.UNSIGNED_INT; } - if (![ - constants.UNSIGNED_BYTE, - constants.FLOAT, - constants.HALF_FLOAT - ].includes(framebuffer.format)) { + if ( + ![ + constants.UNSIGNED_BYTE, + constants.FLOAT, + constants.HALF_FLOAT + ].includes(framebuffer.format) + ) { console.warn( 'Unknown Framebuffer format. ' + 'Please use UNSIGNED_BYTE, FLOAT, or HALF_FLOAT. ' + @@ -1374,10 +1401,12 @@ class RendererGL extends Renderer3D { ); framebuffer.format = constants.UNSIGNED_BYTE; } - if (framebuffer.useDepth && ![ - constants.UNSIGNED_INT, - constants.FLOAT - ].includes(framebuffer.depthFormat)) { + if ( + framebuffer.useDepth && + ![constants.UNSIGNED_INT, constants.FLOAT].includes( + framebuffer.depthFormat + ) + ) { console.warn( 'Unknown Framebuffer depth format. ' + 'Please use UNSIGNED_INT or FLOAT. Defaulting to FLOAT.' @@ -1479,7 +1508,9 @@ class RendererGL extends Renderer3D { gl.framebufferTexture2D( gl.FRAMEBUFFER, - framebuffer.useStencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT, + framebuffer.useStencil + ? gl.DEPTH_STENCIL_ATTACHMENT + : gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0 @@ -1495,7 +1526,10 @@ class RendererGL extends Renderer3D { gl.RENDERBUFFER, Math.max( 0, - Math.min(framebuffer.antialiasSamples, gl.getParameter(gl.MAX_SAMPLES)) + Math.min( + framebuffer.antialiasSamples, + gl.getParameter(gl.MAX_SAMPLES) + ) ), colorFormat.internalFormat, framebuffer.width * framebuffer.density, @@ -1510,7 +1544,10 @@ class RendererGL extends Renderer3D { gl.RENDERBUFFER, Math.max( 0, - Math.min(framebuffer.antialiasSamples, gl.getParameter(gl.MAX_SAMPLES)) + Math.min( + framebuffer.antialiasSamples, + gl.getParameter(gl.MAX_SAMPLES) + ) ), depthFormat.internalFormat, framebuffer.width * framebuffer.density, @@ -1528,7 +1565,9 @@ class RendererGL extends Renderer3D { if (framebuffer.useDepth) { gl.framebufferRenderbuffer( gl.FRAMEBUFFER, - framebuffer.useStencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT, + framebuffer.useStencil + ? gl.DEPTH_STENCIL_ATTACHMENT + : gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, framebuffer.depthRenderbuffer ); @@ -1561,9 +1600,10 @@ class RendererGL extends Renderer3D { if (framebuffer.format === constants.FLOAT) { type = gl.FLOAT; } else if (framebuffer.format === constants.HALF_FLOAT) { - type = this.webglVersion === constants.WEBGL2 - ? gl.HALF_FLOAT - : gl.getExtension('OES_texture_half_float').HALF_FLOAT_OES; + type = + this.webglVersion === constants.WEBGL2 + ? gl.HALF_FLOAT + : gl.getExtension('OES_texture_half_float').HALF_FLOAT_OES; } else { type = gl.UNSIGNED_BYTE; } @@ -1668,11 +1708,13 @@ class RendererGL extends Renderer3D { } deleteFramebufferTextures(framebuffer) { - this._deleteFramebufferTexture(framebuffer.color) + this._deleteFramebufferTexture(framebuffer.color); if (framebuffer.depth) this._deleteFramebufferTexture(framebuffer.depth); const gl = this.GL; - if (framebuffer.colorRenderbuffer) gl.deleteRenderbuffer(framebuffer.colorRenderbuffer); - if (framebuffer.depthRenderbuffer) gl.deleteRenderbuffer(framebuffer.depthRenderbuffer); + if (framebuffer.colorRenderbuffer) + gl.deleteRenderbuffer(framebuffer.colorRenderbuffer); + if (framebuffer.depthRenderbuffer) + gl.deleteRenderbuffer(framebuffer.depthRenderbuffer); } deleteFramebufferResources(framebuffer) { @@ -1705,13 +1747,17 @@ class RendererGL extends Renderer3D { const partsToCopy = { colorTexture: [ gl.COLOR_BUFFER_BIT, - framebuffer.colorP5Texture.magFilter === constants.LINEAR ? gl.LINEAR : gl.NEAREST - ], + framebuffer.colorP5Texture.magFilter === constants.LINEAR + ? gl.LINEAR + : gl.NEAREST + ] }; if (framebuffer.useDepth) { partsToCopy.depthTexture = [ gl.DEPTH_BUFFER_BIT, - framebuffer.depthP5Texture.magFilter === constants.LINEAR ? gl.LINEAR : gl.NEAREST + framebuffer.depthP5Texture.magFilter === constants.LINEAR + ? gl.LINEAR + : gl.NEAREST ]; } const [flag, filter] = partsToCopy[property]; @@ -1737,9 +1783,7 @@ class RendererGL extends Renderer3D { const gl = this.GL; gl.bindFramebuffer( gl.FRAMEBUFFER, - framebuffer - ? this.getFramebufferToBind(framebuffer) - : null + framebuffer ? this.getFramebufferToBind(framebuffer) : null ); } @@ -1818,9 +1862,10 @@ class RendererGL extends Renderer3D { if (channel < channels) { // Find the index of this pixel in `rawData`, which might have a // different number of channels - const rawDataIdx = channels === 4 - ? idx - : (yPos * w * framebuffer.density + xPos) * channels + channel; + const rawDataIdx = + channels === 4 + ? idx + : (yPos * w * framebuffer.density + xPos) * channels + channel; fullData[idx] = rawData[rawDataIdx]; } } @@ -1829,10 +1874,9 @@ class RendererGL extends Renderer3D { // Create image from data const region = new Image(w * framebuffer.density, h * framebuffer.density); - region.imageData = region.canvas.getContext('2d').createImageData( - region.width, - region.height - ); + region.imageData = region.canvas + .getContext('2d') + .createImageData(region.width, region.height); region.imageData.data.set(fullData); region.pixels = region.imageData.data; region.updatePixels(); @@ -1848,10 +1892,19 @@ class RendererGL extends Renderer3D { const colorFormat = this._getFramebufferColorFormat(framebuffer); const channels = colorFormat.format === gl.RGBA ? 4 : 3; - const len = framebuffer.width * framebuffer.height * framebuffer.density * framebuffer.density * channels; - const TypedArrayClass = colorFormat.type === gl.UNSIGNED_BYTE ? Uint8Array : Float32Array; + const len = + framebuffer.width * + framebuffer.height * + framebuffer.density * + framebuffer.density * + channels; + const TypedArrayClass = + colorFormat.type === gl.UNSIGNED_BYTE ? Uint8Array : Float32Array; - if (!(framebuffer.pixels instanceof TypedArrayClass) || framebuffer.pixels.length !== len) { + if ( + !(framebuffer.pixels instanceof TypedArrayClass) || + framebuffer.pixels.length !== len + ) { throw new Error( 'The pixels array has not been set correctly. Please call loadPixels() before updatePixels().' ); @@ -1890,10 +1943,14 @@ class RendererGL extends Renderer3D { this._drawingFilter = true; this.image( framebuffer, - 0, 0, - framebuffer.width, framebuffer.height, - -this.width / 2, -this.height / 2, - this.width, this.height + 0, + 0, + framebuffer.width, + framebuffer.height, + -this.width / 2, + -this.height / 2, + this.width, + this.height ); this._drawingFilter = false; this.pop(); @@ -1911,7 +1968,6 @@ class RendererGL extends Renderer3D { this.bindFramebuffer(prevFramebuffer); } } - } function rendererGL(p5, fn) { @@ -2081,6 +2137,6 @@ function rendererGL(p5, fn) { export default rendererGL; export { RendererGL }; -if (typeof p5 !== "undefined") { +if (typeof p5 !== 'undefined') { rendererGL(p5, p5.prototype); } diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js index c39de0bd71..b99eb1e8a0 100644 --- a/src/webgl/p5.Shader.js +++ b/src/webgl/p5.Shader.js @@ -12,9 +12,9 @@ class Shader { // Detect compute shader: first arg is STRING and second is undefined OR an options object if ( - typeof vertSrc === 'string' && ( - fragSrc === undefined || (typeof fragSrc === 'object' && !Array.isArray(fragSrc)) - ) + typeof vertSrc === 'string' && + (fragSrc === undefined || + (typeof fragSrc === 'object' && !Array.isArray(fragSrc))) ) { // Compute shader this.shaderType = 'compute'; @@ -76,7 +76,7 @@ class Shader { modified: { vertex: (options.modified && options.modified.vertex) || {}, fragment: (options.modified && options.modified.fragment) || {}, - compute: (options.modified && options.modified.compute) || {}, + compute: (options.modified && options.modified.compute) || {} } }; } @@ -470,18 +470,29 @@ class Shader { declarations: (this.hooks.declarations || '') + '\n' + (hooks.declarations || ''), uniforms: Object.assign({}, this.hooks.uniforms, hooks.uniforms || {}), - storageUniforms: Object.assign({}, this.hooks.storageUniforms, hooks.storageUniforms || {}), - varyingVariables: (hooks.varyingVariables || []).concat(this.hooks.varyingVariables || []), - instanceIDVarying: hooks.instanceIDVarying || this.hooks.instanceIDVarying || null, + storageUniforms: Object.assign( + {}, + this.hooks.storageUniforms, + hooks.storageUniforms || {} + ), + varyingVariables: (hooks.varyingVariables || []).concat( + this.hooks.varyingVariables || [] + ), + instanceIDVarying: + hooks.instanceIDVarying || this.hooks.instanceIDVarying || null, fragment: Object.assign({}, this.hooks.fragment, newHooks.fragment || {}), vertex: Object.assign({}, this.hooks.vertex, newHooks.vertex || {}), compute: Object.assign({}, this.hooks.compute, newHooks.compute || {}), helpers: Object.assign({}, this.hooks.helpers, newHooks.helpers || {}), - hookAliases: Object.assign({}, this.hooks.hookAliases, newHooks.hookAliases || {}), + hookAliases: Object.assign( + {}, + this.hooks.hookAliases, + newHooks.hookAliases || {} + ), modified: { vertex: modifiedVertex, fragment: modifiedFragment, - compute: modifiedCompute, + compute: modifiedCompute } }); @@ -539,7 +550,8 @@ class Shader { } for (const name in this.hooks.storageUniforms) { const initializer = this.hooks.storageUniforms[name]; - const value = initializer instanceof Function ? initializer() : initializer; + const value = + initializer instanceof Function ? initializer() : initializer; if (value !== undefined && value !== null) { this.setUniform(name, value); } @@ -776,7 +788,7 @@ class Shader { for (const meta of uniformMetadata) { const uniform = { ...meta, - _cachedData: undefined, + _cachedData: undefined }; if (uniform.isSampler) { @@ -830,14 +842,12 @@ class Shader { let tex = uniform.texture; if ( tex === undefined || - ( - // Make sure we unbind a framebuffer uniform if it's the same - // framebuffer that is actvely being drawn to in order to - // prevent a feedback cycle - tex.isFramebufferTexture && + // Make sure we unbind a framebuffer uniform if it's the same + // framebuffer that is actvely being drawn to in order to + // prevent a feedback cycle + (tex.isFramebufferTexture && !tex.src.framebuffer.antialias && - tex.src.framebuffer === this._renderer.activeFramebuffer() - ) + tex.src.framebuffer === this._renderer.activeFramebuffer()) ) { // user hasn't yet supplied a texture for this slot. // (or there may not be one--maybe just lighting), @@ -1112,7 +1122,10 @@ class Shader { // duck typing instead of instanceof to avoid accidentally importing // and comparing against a separate copy of p5 classes if (data?.isVector) { - data = data.values.length !== data.dimensions ? data.values.slice(0, data.dimensions) : data.values; + data = + data.values.length !== data.dimensions + ? data.values.slice(0, data.dimensions) + : data.values; } else if (data?.isColor) { data = data._getRGBA([1, 1, 1, 1]); } @@ -1130,7 +1143,10 @@ class Shader { return; } else { if (Array.isArray(data) || data instanceof TypedArray) { - if (uniform._cachedData && this._renderer._arraysEqual(uniform._cachedData, data)) { + if ( + uniform._cachedData && + this._renderer._arraysEqual(uniform._cachedData, data) + ) { return; } uniform._cachedData = data.slice(0); @@ -1159,14 +1175,22 @@ class Shader { } if (attr.location !== -1) { - this._renderer._enableAttrib(this, attr, size, type, normalized, stride, offset); + this._renderer._enableAttrib( + this, + attr, + size, + type, + normalized, + stride, + offset + ); } } return this; } -}; +} -function shader(p5, fn){ +function shader(p5, fn) { /** * A class to describe a shader program. * @@ -1306,6 +1330,6 @@ function shader(p5, fn){ export default shader; export { Shader }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { shader(p5, p5.prototype); } diff --git a/src/webgl/p5.Texture.js b/src/webgl/p5.Texture.js index 3c12e6ed69..339787af40 100644 --- a/src/webgl/p5.Texture.js +++ b/src/webgl/p5.Texture.js @@ -14,7 +14,7 @@ import { Graphics } from '../core/p5.Graphics'; import { FramebufferTexture } from './p5.Framebuffer'; class Texture { - constructor (renderer, obj, settings = {}) { + constructor(renderer, obj, settings = {}) { this._renderer = renderer; this.src = obj; @@ -72,7 +72,8 @@ class Texture { this.isSrcP5Image = obj instanceof Image; this.isSrcP5Graphics = obj instanceof Graphics; this.isSrcP5Renderer = obj instanceof Renderer; - this.isImageData = typeof ImageData !== 'undefined' && obj instanceof ImageData; + this.isImageData = + typeof ImageData !== 'undefined' && obj instanceof ImageData; this.isSrcMediaElement = typeof MediaElement !== 'undefined' && obj instanceof MediaElement; this.isSrcHTMLElement = @@ -90,17 +91,14 @@ class Texture { } } - _getTextureDataFromSource () { + _getTextureDataFromSource() { let textureData; if (this.isFramebufferTexture) { textureData = this.src.rawTexture(); } else if (this.isSrcP5Image) { - // param is a p5.Image + // param is a p5.Image textureData = this.src.canvas; - } else if ( - this.isSrcMediaElement || - this.isSrcHTMLElement - ) { + } else if (this.isSrcMediaElement || this.isSrcHTMLElement) { // createCapture elements that are flipped need // to go through a canvas if (this.isSrcMediaElement && this.src.flipped) { @@ -132,10 +130,12 @@ class Texture { format: this.format, dataType: this.dataType, width: textureData.width, - height: textureData.height, + height: textureData.height }); } else { - this.textureHandle = this._renderer.createFramebufferTextureHandle(this.src); + this.textureHandle = this._renderer.createFramebufferTextureHandle( + this.src + ); } this._renderer.setTextureParams(this, { @@ -156,10 +156,7 @@ class Texture { ); } else if (!this.isFramebufferTexture) { // this.update() - this._renderer.uploadTextureFromSource( - this.textureHandle, - textureData - ); + this._renderer.uploadTextureFromSource(this.textureHandle, textureData); } this.unbindTexture(); @@ -283,7 +280,7 @@ class Texture { return this; } - unbindTexture () { + unbindTexture() { this._renderer.unbindTexture(); } @@ -343,7 +340,7 @@ class MipmapTexture extends Texture { format: this.format, dataType: this.dataType, width: this.width, - height: this.height, + height: this.height }); } else { // WebGPU path: levels is a mipmapData object with pre-built GPU texture @@ -356,7 +353,7 @@ class MipmapTexture extends Texture { format: levels.format, dataType: 'uint8', width: this.width, - height: this.height, + height: this.height }); } @@ -371,7 +368,7 @@ class MipmapTexture extends Texture { update() {} } -function texture(p5, fn){ +function texture(p5, fn) { /** * Texture class for WEBGL Mode * @private @@ -411,6 +408,6 @@ function texture(p5, fn){ export default texture; export { Texture, MipmapTexture }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== 'undefined') { texture(p5, p5.prototype); } diff --git a/src/webgl/shaderHookUtils.js b/src/webgl/shaderHookUtils.js index 8199746130..8a7170fe1f 100644 --- a/src/webgl/shaderHookUtils.js +++ b/src/webgl/shaderHookUtils.js @@ -22,7 +22,9 @@ export function getShaderHookTypes(shader, hookName) { throw new Error(`Couldn't find function parameters in hook body:\n${body}`); } const structProperties = structName => { - const structDefMatch = new RegExp(`struct\\s+${structName}\\s*{([^}]*)}`).exec(fullSrc); + const structDefMatch = new RegExp( + `struct\\s+${structName}\\s*{([^}]*)}` + ).exec(fullSrc); if (!structDefMatch) return undefined; const properties = []; for (const defSrc of structDefMatch[1].split(';')) { @@ -39,7 +41,7 @@ export function getShaderHookTypes(shader, hookName) { typeName, qualifiers: [], properties: typeProperties, - dataType, + dataType } }); } @@ -60,7 +62,7 @@ export function getShaderHookTypes(shader, hookName) { typeName, qualifiers, properties, - dataType, + dataType } }; }); @@ -71,7 +73,7 @@ export function getShaderHookTypes(shader, hookName) { typeName: returnType, qualifiers: returnQualifiers, properties: structProperties(returnType), - dataType, + dataType }, parameters }; diff --git a/src/webgl/strands_glslBackend.js b/src/webgl/strands_glslBackend.js index 75bd44a2c1..44f9ebf7c1 100644 --- a/src/webgl/strands_glslBackend.js +++ b/src/webgl/strands_glslBackend.js @@ -1,8 +1,20 @@ import noiseGLSL from './shaders/functions/noise3DGLSL.glsl'; import randomGLSL from './shaders/functions/randomGLSL.glsl'; import randomVertGLSL from './shaders/functions/randomVertGLSL.glsl'; -import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType, INSTANCE_ID_VARYING_NAME, HOOK_PARAM_PREFIX } from "../strands/ir_types"; -import { getNodeDataFromID, extractNodeTypeInfo } from "../strands/ir_dag"; +import { + NodeType, + OpCodeToSymbol, + BlockType, + OpCode, + NodeTypeToName, + isStructType, + BaseType, + StatementType, + DataType, + INSTANCE_ID_VARYING_NAME, + HOOK_PARAM_PREFIX +} from '../strands/ir_types'; +import { getNodeDataFromID, extractNodeTypeInfo } from '../strands/ir_dag'; import * as FES from '../strands/strands_FES'; import * as build from '../strands/ir_builders'; function shouldCreateTemp(dag, nodeID) { @@ -13,22 +25,22 @@ function shouldCreateTemp(dag, nodeID) { return uses.length > 1; } const TypeNames = { - 'float1': 'float', - 'float2': 'vec2', - 'float3': 'vec3', - 'float4': 'vec4', - 'int1': 'int', - 'int2': 'ivec2', - 'int3': 'ivec3', - 'int4': 'ivec4', - 'bool1': 'bool', - 'bool2': 'bvec2', - 'bool3': 'bvec3', - 'bool4': 'bvec4', - 'mat2': 'mat2x2', - 'mat3': 'mat3x3', - 'mat4': 'mat4x4', -} + float1: 'float', + float2: 'vec2', + float3: 'vec3', + float4: 'vec4', + int1: 'int', + int2: 'ivec2', + int3: 'ivec3', + int4: 'ivec4', + bool1: 'bool', + bool2: 'bvec2', + bool3: 'bvec3', + bool4: 'bvec4', + mat2: 'mat2x2', + mat3: 'mat3x3', + mat4: 'mat4x4' +}; const cfgHandlers = { [BlockType.DEFAULT]: (blockID, strandsContext, generationContext) => { const { dag, cfg } = strandsContext; @@ -36,7 +48,11 @@ const cfgHandlers = { for (const nodeID of instructions) { const nodeType = dag.nodeTypes[nodeID]; if (shouldCreateTemp(dag, nodeID)) { - const declaration = glslBackend.generateDeclaration(generationContext, dag, nodeID); + const declaration = glslBackend.generateDeclaration( + generationContext, + dag, + nodeID + ); generationContext.write(declaration); } if (nodeType === NodeType.STATEMENT) { @@ -80,7 +96,11 @@ const cfgHandlers = { [BlockType.IF_COND](blockID, strandsContext, generationContext) { const { dag, cfg } = strandsContext; const conditionID = cfg.blockConditions[blockID]; - const condExpr = glslBackend.generateExpression(generationContext, dag, conditionID); + const condExpr = glslBackend.generateExpression( + generationContext, + dag, + conditionID + ); generationContext.write(`if (${condExpr})`); this[BlockType.DEFAULT](blockID, strandsContext, generationContext); }, @@ -124,7 +144,11 @@ const cfgHandlers = { generationContext.suppressSemicolon = isLast; if (shouldCreateTemp(dag, nodeID)) { - const declaration = glslBackend.generateDeclaration(generationContext, dag, nodeID); + const declaration = glslBackend.generateDeclaration( + generationContext, + dag, + nodeID + ); generationContext.write(declaration); } if (node.nodeType === NodeType.STATEMENT) { @@ -156,20 +180,26 @@ const cfgHandlers = { const sourceNodeID = node.dependsOn[branchIndex]; const tempName = generationContext.tempNames[nodeID]; if (tempName && sourceNodeID !== null) { - const sourceExpr = glslBackend.generateExpression(generationContext, dag, sourceNodeID); + const sourceExpr = glslBackend.generateExpression( + generationContext, + dag, + sourceNodeID + ); generationContext.write(`${tempName} = ${sourceExpr};`); } } } } } - }, -} + } +}; export const glslBackend = { hookEntry(hookType) { - const firstLine = `(${hookType.parameters.flatMap((param) => { - return `${param.qualifiers?.length ? param.qualifiers.join(' ') : ''}${param.type.typeName} ${HOOK_PARAM_PREFIX}${param.name}`; - }).join(', ')}) {`; + const firstLine = `(${hookType.parameters + .flatMap(param => { + return `${param.qualifiers?.length ? param.qualifiers.join(' ') : ''}${param.type.typeName} ${HOOK_PARAM_PREFIX}${param.name}`; + }) + .join(', ')}) {`; return firstLine; }, getNoiseShaderSnippet() { @@ -182,7 +212,7 @@ export const glslBackend = { return randomVertGLSL; }, getTypeName(baseType, dimension) { - const primitiveTypeName = TypeNames[baseType + dimension] + const primitiveTypeName = TypeNames[baseType + dimension]; if (!primitiveTypeName) { return baseType; } @@ -225,8 +255,16 @@ export const glslBackend = { const sourceNodeID = node.dependsOn[1]; // Generate the target expression (could be variable or swizzle) - const targetExpr = this.generateExpression(generationContext, dag, targetNodeID); - const sourceExpr = this.generateExpression(generationContext, dag, sourceNodeID); + const targetExpr = this.generateExpression( + generationContext, + dag, + targetNodeID + ); + const sourceExpr = this.generateExpression( + generationContext, + dag, + sourceNodeID + ); const semicolon = generationContext.suppressSemicolon ? '' : ';'; // Generate assignment if we have both target and source @@ -242,7 +280,12 @@ export const glslBackend = { const typeName = this.getTypeName(T.baseType, T.dimension); return `${typeName} ${tmp} = ${expr};`; }, - generateReturnStatement(strandsContext, generationContext, rootNodeID, returnType) { + generateReturnStatement( + strandsContext, + generationContext, + rootNodeID, + returnType + ) { if (!returnType) { generationContext.write('return;'); return; @@ -253,15 +296,21 @@ export const glslBackend = { const structTypeInfo = returnType; for (let i = 0; i < structTypeInfo.properties.length; i++) { const prop = structTypeInfo.properties[i]; - const val = this.generateExpression(generationContext, dag, rootNode.dependsOn[i]); + const val = this.generateExpression( + generationContext, + dag, + rootNode.dependsOn[i] + ); if (prop.name !== val) { generationContext.write( `${rootNode.identifier}.${prop.name} = ${val};` - ) + ); } } } - generationContext.write(`return ${this.generateExpression(generationContext, dag, rootNodeID)};`); + generationContext.write( + `return ${this.generateExpression(generationContext, dag, rootNodeID)};` + ); }, generateExpression(generationContext, dag, nodeID) { const node = getNodeDataFromID(dag, nodeID); @@ -270,136 +319,184 @@ export const glslBackend = { } switch (node.nodeType) { case NodeType.LITERAL: - if (node.baseType === BaseType.FLOAT) { - return node.value.toFixed(4); - } - else { - return node.value; - } + if (node.baseType === BaseType.FLOAT) { + return node.value.toFixed(4); + } else { + return node.value; + } case NodeType.VARIABLE: - // Track shared variable usage context - if (generationContext.shaderContext && generationContext.strandsContext?.sharedVariables?.has(node.identifier)) { - const sharedVar = generationContext.strandsContext.sharedVariables.get(node.identifier); - if (generationContext.shaderContext === 'vertex') { - sharedVar.usedInVertex = true; - } else if (generationContext.shaderContext === 'fragment') { - sharedVar.usedInFragment = true; + // Track shared variable usage context + if ( + generationContext.shaderContext && + generationContext.strandsContext?.sharedVariables?.has( + node.identifier + ) + ) { + const sharedVar = + generationContext.strandsContext.sharedVariables.get( + node.identifier + ); + if (generationContext.shaderContext === 'vertex') { + sharedVar.usedInVertex = true; + } else if (generationContext.shaderContext === 'fragment') { + sharedVar.usedInFragment = true; + } } - } - // Detect instanceID usage in fragment context and rewrite to varying name - if (node.identifier === this.instanceIdReference() && generationContext.shaderContext === 'fragment') { - generationContext.strandsContext._instanceIDUsedInFragment = true; - return INSTANCE_ID_VARYING_NAME; - } + // Detect instanceID usage in fragment context and rewrite to varying name + if ( + node.identifier === this.instanceIdReference() && + generationContext.shaderContext === 'fragment' + ) { + generationContext.strandsContext._instanceIDUsedInFragment = true; + return INSTANCE_ID_VARYING_NAME; + } - return node.identifier; + return node.identifier; case NodeType.OPERATION: - const useParantheses = node.usedBy.length > 0; - if (node.opCode === OpCode.Nary.CONSTRUCTOR) { - // TODO: differentiate casts and constructors for more efficient codegen. - // if (node.dependsOn.length === 1 && node.dimension === 1) { - // return this.generateExpression(generationContext, dag, node.dependsOn[0]); - // } - if (node.baseType === BaseType.SAMPLER2D) { - return this.generateExpression(generationContext, dag, node.dependsOn[0]); + const useParantheses = node.usedBy.length > 0; + if (node.opCode === OpCode.Nary.CONSTRUCTOR) { + // TODO: differentiate casts and constructors for more efficient codegen. + // if (node.dependsOn.length === 1 && node.dimension === 1) { + // return this.generateExpression(generationContext, dag, node.dependsOn[0]); + // } + if (node.baseType === BaseType.SAMPLER2D) { + return this.generateExpression( + generationContext, + dag, + node.dependsOn[0] + ); + } + const T = this.getTypeName(node.baseType, node.dimension); + const deps = node.dependsOn.map(dep => + this.generateExpression(generationContext, dag, dep) + ); + return `${T}(${deps.join(', ')})`; } - const T = this.getTypeName(node.baseType, node.dimension); - const deps = node.dependsOn.map((dep) => this.generateExpression(generationContext, dag, dep)); - return `${T}(${deps.join(', ')})`; - } - if (node.opCode === OpCode.Nary.FUNCTION_CALL) { - const functionArgs = node.dependsOn.map(arg =>this.generateExpression(generationContext, dag, arg)); - return `${node.identifier}(${functionArgs.join(', ')})`; - } - if (node.opCode === OpCode.Nary.TERNARY) { - const [condID, trueID, falseID] = node.dependsOn; - const cond = this.generateExpression(generationContext, dag, condID); - const trueExpr = this.generateExpression(generationContext, dag, trueID); - const falseExpr = this.generateExpression(generationContext, dag, falseID); - return `(${cond} ? ${trueExpr} : ${falseExpr})`; - } - if (node.opCode === OpCode.Binary.MEMBER_ACCESS) { - const [lID, rID] = node.dependsOn; - const lName = this.generateExpression(generationContext, dag, lID); - const rName = this.generateExpression(generationContext, dag, rID); - return `${lName}.${rName}`; - } - if (node.opCode === OpCode.Unary.SWIZZLE) { - const parentID = node.dependsOn[0]; - const parentExpr = this.generateExpression(generationContext, dag, parentID); - return `${parentExpr}.${node.swizzle}`; - } - if (node.opCode === OpCode.Binary.ARRAY_ACCESS) { - const [bufferID, indexID] = node.dependsOn; - const bufferExpr = this.generateExpression(generationContext, dag, bufferID); - const indexExpr = this.generateExpression(generationContext, dag, indexID); - return `${bufferExpr}[${indexExpr}]`; - } - if (node.dependsOn.length === 2) { - const [lID, rID] = node.dependsOn; - const left = this.generateExpression(generationContext, dag, lID); - const right = this.generateExpression(generationContext, dag, rID); + if (node.opCode === OpCode.Nary.FUNCTION_CALL) { + const functionArgs = node.dependsOn.map(arg => + this.generateExpression(generationContext, dag, arg) + ); + return `${node.identifier}(${functionArgs.join(', ')})`; + } + if (node.opCode === OpCode.Nary.TERNARY) { + const [condID, trueID, falseID] = node.dependsOn; + const cond = this.generateExpression(generationContext, dag, condID); + const trueExpr = this.generateExpression( + generationContext, + dag, + trueID + ); + const falseExpr = this.generateExpression( + generationContext, + dag, + falseID + ); + return `(${cond} ? ${trueExpr} : ${falseExpr})`; + } + if (node.opCode === OpCode.Binary.MEMBER_ACCESS) { + const [lID, rID] = node.dependsOn; + const lName = this.generateExpression(generationContext, dag, lID); + const rName = this.generateExpression(generationContext, dag, rID); + return `${lName}.${rName}`; + } + if (node.opCode === OpCode.Unary.SWIZZLE) { + const parentID = node.dependsOn[0]; + const parentExpr = this.generateExpression( + generationContext, + dag, + parentID + ); + return `${parentExpr}.${node.swizzle}`; + } + if (node.opCode === OpCode.Binary.ARRAY_ACCESS) { + const [bufferID, indexID] = node.dependsOn; + const bufferExpr = this.generateExpression( + generationContext, + dag, + bufferID + ); + const indexExpr = this.generateExpression( + generationContext, + dag, + indexID + ); + return `${bufferExpr}[${indexExpr}]`; + } + if (node.dependsOn.length === 2) { + const [lID, rID] = node.dependsOn; + const left = this.generateExpression(generationContext, dag, lID); + const right = this.generateExpression(generationContext, dag, rID); - // Special case for modulo: use mod() function for floats in GLSL - if (node.opCode === OpCode.Binary.MODULO) { - const leftNode = getNodeDataFromID(dag, lID); - const rightNode = getNodeDataFromID(dag, rID); - // If either operand is float, use mod() function - if (leftNode.baseType === BaseType.FLOAT || rightNode.baseType === BaseType.FLOAT) { - return `mod(${left}, ${right})`; + // Special case for modulo: use mod() function for floats in GLSL + if (node.opCode === OpCode.Binary.MODULO) { + const leftNode = getNodeDataFromID(dag, lID); + const rightNode = getNodeDataFromID(dag, rID); + // If either operand is float, use mod() function + if ( + leftNode.baseType === BaseType.FLOAT || + rightNode.baseType === BaseType.FLOAT + ) { + return `mod(${left}, ${right})`; + } + // For integers, use % operator + return `(${left} % ${right})`; } - // For integers, use % operator - return `(${left} % ${right})`; - } - const opSym = OpCodeToSymbol[node.opCode]; - if (useParantheses) { - return `(${left} ${opSym} ${right})`; - } else { - return `${left} ${opSym} ${right}`; + const opSym = OpCodeToSymbol[node.opCode]; + if (useParantheses) { + return `(${left} ${opSym} ${right})`; + } else { + return `${left} ${opSym} ${right}`; + } } - } - if (node.opCode === OpCode.Unary.LOGICAL_NOT - || node.opCode === OpCode.Unary.NEGATE - || node.opCode === OpCode.Unary.PLUS + if ( + node.opCode === OpCode.Unary.LOGICAL_NOT || + node.opCode === OpCode.Unary.NEGATE || + node.opCode === OpCode.Unary.PLUS ) { - const [i] = node.dependsOn; - const val = this.generateExpression(generationContext, dag, i); - const sym = OpCodeToSymbol[node.opCode]; - return `${sym}${val}`; - } + const [i] = node.dependsOn; + const val = this.generateExpression(generationContext, dag, i); + const sym = OpCodeToSymbol[node.opCode]; + return `${sym}${val}`; + } case NodeType.PHI: - // Phi nodes represent conditional merging of values - // If this phi node has an identifier (like varying variables), use that - if (node.identifier) { - return node.identifier; - } - // Otherwise, they should have been declared as temporary variables - // and assigned in the appropriate branches - if (generationContext.tempNames?.[nodeID]) { - return generationContext.tempNames[nodeID]; - } else { - // If no temp was created, this phi node only has one input - // so we can just use that directly - const validInputs = node.dependsOn.filter(id => id !== null); - if (validInputs.length > 0) { - return this.generateExpression(generationContext, dag, validInputs[0]); + // Phi nodes represent conditional merging of values + // If this phi node has an identifier (like varying variables), use that + if (node.identifier) { + return node.identifier; + } + // Otherwise, they should have been declared as temporary variables + // and assigned in the appropriate branches + if (generationContext.tempNames?.[nodeID]) { + return generationContext.tempNames[nodeID]; } else { - throw new Error(`No valid inputs for node`) - // Fallback: create a default value - const typeName = this.getTypeName(node.baseType, node.dimension); - if (node.dimension === 1) { - return node.baseType === BaseType.FLOAT ? '0.0' : '0'; + // If no temp was created, this phi node only has one input + // so we can just use that directly + const validInputs = node.dependsOn.filter(id => id !== null); + if (validInputs.length > 0) { + return this.generateExpression( + generationContext, + dag, + validInputs[0] + ); } else { - return `${typeName}(0.0)`; + throw new Error(`No valid inputs for node`); + // Fallback: create a default value + const typeName = this.getTypeName(node.baseType, node.dimension); + if (node.dimension === 1) { + return node.baseType === BaseType.FLOAT ? '0.0' : '0'; + } else { + return `${typeName}(0.0)`; + } } } - } case NodeType.ASSIGNMENT: - FES.internalError(`ASSIGNMENT nodes should not be used as expressions`) + FES.internalError(`ASSIGNMENT nodes should not be used as expressions`); default: - FES.internalError(`${NodeTypeToName[node.nodeType]} code generation not implemented yet`) + FES.internalError( + `${NodeTypeToName[node.nodeType]} code generation not implemented yet` + ); } }, generateBlock(blockID, strandsContext, generationContext) { @@ -411,12 +508,19 @@ export const glslBackend = { createGetTextureCall(strandsContext, args) { // In GLSL, getTexture is straightforward - just pass through the args // First argument should be a texture (sampler2D), second should be coordinates - const { id, dimension } = build.functionCallNode(strandsContext, 'getTexture', args, { - overloads: [{ - params: [DataType.sampler2D, DataType.float2], - returnType: DataType.float4 - }] - }); + const { id, dimension } = build.functionCallNode( + strandsContext, + 'getTexture', + args, + { + overloads: [ + { + params: [DataType.sampler2D, DataType.float2], + returnType: DataType.float4 + } + ] + } + ); return { id, dimension }; }, @@ -425,6 +529,11 @@ export const glslBackend = { }, generateInstanceIDVarying() { - return { name: INSTANCE_ID_VARYING_NAME, declaration: `int ${INSTANCE_ID_VARYING_NAME}`, source: 'gl_InstanceID', interpolation: 'flat' }; - }, -} + return { + name: INSTANCE_ID_VARYING_NAME, + declaration: `int ${INSTANCE_ID_VARYING_NAME}`, + source: 'gl_InstanceID', + interpolation: 'flat' + }; + } +}; diff --git a/src/webgl/text.js b/src/webgl/text.js index 2a2f5d66d3..f10c8e8ea1 100644 --- a/src/webgl/text.js +++ b/src/webgl/text.js @@ -5,12 +5,12 @@ import { Geometry } from './p5.Geometry'; import { Font, arrayCommandsToObjects } from '../type/p5.Font'; function text(p5, fn) { - Renderer3D.prototype.maxCachedGlyphs = function() { + Renderer3D.prototype.maxCachedGlyphs = function () { // TODO: use more than vibes to find a good value for this return 200; }; - Font.prototype._getFontInfo = function(axs) { + Font.prototype._getFontInfo = function (axs) { // For WebGL, a cache of font data to use on the GPU. this._fontInfos = this._fontInfos || {}; @@ -241,7 +241,7 @@ function text(p5, fn) { * find the minimum & maximum value in a list of values */ function minMax(rg, min, max) { - for (let i = rg.length; i-- > 0; ) { + for (let i = rg.length; i-- > 0;) { const v = rg[i]; if (min > v) min = v; if (max < v) max = v; diff --git a/src/webgl/utils.js b/src/webgl/utils.js index 2d79b14578..0d29e143e4 100644 --- a/src/webgl/utils.js +++ b/src/webgl/utils.js @@ -1,6 +1,6 @@ -import * as constants from "../core/constants"; -import { INSTANCE_ID_VARYING_NAME } from "../strands/ir_types"; -import { Texture } from "./p5.Texture"; +import * as constants from '../core/constants'; +import { INSTANCE_ID_VARYING_NAME } from '../strands/ir_types'; +import { Texture } from './p5.Texture'; /** * @private @@ -27,7 +27,7 @@ export function readPixelsWebGL( height, format, type, - flipY, + flipY ) { // Record the currently bound framebuffer so we can go back to it after, and // bind the framebuffer we want to read from @@ -50,7 +50,7 @@ export function readPixelsWebGL( height, format, type, - pixels, + pixels ); // Re-bind whatever was previously bound @@ -105,15 +105,18 @@ export function readPixelWebGL(gl, framebuffer, x, y, format, type, flipY) { export function setWebGLTextureParams(texture, gl, webglVersion) { texture.bindTexture(); const glMinFilter = - texture.minFilter === constants.NEAREST ? gl.NEAREST : - texture.minFilter === constants.LINEAR_MIPMAP ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR; + texture.minFilter === constants.NEAREST + ? gl.NEAREST + : texture.minFilter === constants.LINEAR_MIPMAP + ? gl.LINEAR_MIPMAP_LINEAR + : gl.LINEAR; const glMagFilter = texture.magFilter === constants.NEAREST ? gl.NEAREST : gl.LINEAR; // for webgl 1 we need to check if the texture is power of two // if it isn't we will set the wrap mode to CLAMP // webgl2 will support npot REPEAT and MIRROR but we don't check for it yet - const isPowerOfTwo = (x) => (x & (x - 1)) === 0; + const isPowerOfTwo = x => (x & (x - 1)) === 0; const textureData = texture._getTextureDataFromSource(); let wrapWidth; @@ -139,7 +142,7 @@ export function setWebGLTextureParams(texture, gl, webglVersion) { glWrapS = gl.REPEAT; } else { console.warn( - "You tried to set the wrap mode to REPEAT but the texture size is not a power of two. Setting to CLAMP instead", + 'You tried to set the wrap mode to REPEAT but the texture size is not a power of two. Setting to CLAMP instead' ); glWrapS = gl.CLAMP_TO_EDGE; } @@ -151,7 +154,7 @@ export function setWebGLTextureParams(texture, gl, webglVersion) { glWrapS = gl.MIRRORED_REPEAT; } else { console.warn( - "You tried to set the wrap mode to MIRROR but the texture size is not a power of two. Setting to CLAMP instead", + 'You tried to set the wrap mode to MIRROR but the texture size is not a power of two. Setting to CLAMP instead' ); glWrapS = gl.CLAMP_TO_EDGE; } @@ -168,7 +171,7 @@ export function setWebGLTextureParams(texture, gl, webglVersion) { glWrapT = gl.REPEAT; } else { console.warn( - "You tried to set the wrap mode to REPEAT but the texture size is not a power of two. Setting to CLAMP instead", + 'You tried to set the wrap mode to REPEAT but the texture size is not a power of two. Setting to CLAMP instead' ); glWrapT = gl.CLAMP_TO_EDGE; } @@ -180,7 +183,7 @@ export function setWebGLTextureParams(texture, gl, webglVersion) { glWrapT = gl.MIRRORED_REPEAT; } else { console.warn( - "You tried to set the wrap mode to MIRROR but the texture size is not a power of two. Setting to CLAMP instead", + 'You tried to set the wrap mode to MIRROR but the texture size is not a power of two. Setting to CLAMP instead' ); glWrapT = gl.CLAMP_TO_EDGE; } @@ -274,16 +277,16 @@ export function setWebGLUniformValue(shader, uniform, data, getTexture, gl) { } break; case gl.SAMPLER_2D: - if (typeof data == "number") { + if (typeof data == 'number') { if ( data < gl.TEXTURE0 || data > gl.TEXTURE31 || data !== Math.ceil(data) ) { console.log( - "🌸 p5.js says: " + + '🌸 p5.js says: ' + "You're trying to use a number as the data for a texture." + - "Please use a texture.", + 'Please use a texture.' ); return; } @@ -309,7 +312,7 @@ export function setWebGLUniformValue(shader, uniform, data, getTexture, gl) { case gl.UNSIGNED_INT_SAMPLER_3D: case gl.UNSIGNED_INT_SAMPLER_CUBE: case gl.UNSIGNED_INT_SAMPLER_2D_ARRAY: - if (typeof data !== "number") { + if (typeof data !== 'number') { break; } if ( @@ -318,9 +321,9 @@ export function setWebGLUniformValue(shader, uniform, data, getTexture, gl) { data !== Math.ceil(data) ) { console.log( - "🌸 p5.js says: " + + '🌸 p5.js says: ' + "You're trying to use a number as the data for a texture." + - "Please use a texture.", + 'Please use a texture.' ); break; } @@ -350,7 +353,7 @@ export function getWebGLUniformMetadata(shader, gl) { //off here. The size property tells us that its an array //so we dont lose any information by doing this if (uniformInfo.size > 1) { - uniformName = uniformName.substring(0, uniformName.indexOf("[0]")); + uniformName = uniformName.substring(0, uniformName.indexOf('[0]')); } uniform.name = uniformName; uniform.type = uniformInfo.type; @@ -384,7 +387,7 @@ export function getWebGLShaderAttributes(shader, gl) { const numAttributes = gl.getProgramParameter( shader._glProgram, - gl.ACTIVE_ATTRIBUTES, + gl.ACTIVE_ATTRIBUTES ); for (let i = 0; i < numAttributes; ++i) { const attributeInfo = gl.getActiveAttrib(shader._glProgram, i); @@ -403,21 +406,21 @@ export function getWebGLShaderAttributes(shader, gl) { } export function populateGLSLHooks(shader, src, shaderType) { - const main = "void main"; + const main = 'void main'; if (!src.includes(main)) return src; let [preMain, postMain] = src.split(main); - let hooks = ""; - let defines = ""; + let hooks = ''; + let defines = ''; for (const key in shader.hooks.uniforms) { hooks += `uniform ${key};\n`; } if (shader.hooks.declarations) { - hooks += shader.hooks.declarations + "\n"; + hooks += shader.hooks.declarations + '\n'; } if (shader.hooks[shaderType].declarations) { - hooks += shader.hooks[shaderType].declarations + "\n"; + hooks += shader.hooks[shaderType].declarations + '\n'; } // Handle varying variables from p5.strands @@ -427,9 +430,9 @@ export function populateGLSLHooks(shader, src, shaderType) { ) { for (const varyingVar of shader.hooks.varyingVariables) { // Generate OUT declaration for vertex shader, IN declaration for fragment shader - if (shaderType === "vertex") { + if (shaderType === 'vertex') { hooks += `OUT ${varyingVar};\n`; - } else if (shaderType === "fragment") { + } else if (shaderType === 'fragment') { hooks += `IN ${varyingVar};\n`; } } @@ -437,13 +440,17 @@ export function populateGLSLHooks(shader, src, shaderType) { // Handle instanceID varying for fragment access if (shader.hooks.instanceIDVarying) { - const { declaration, source, interpolation } = shader.hooks.instanceIDVarying; + const { declaration, source, interpolation } = + shader.hooks.instanceIDVarying; const qualifier = interpolation ? `${interpolation} ` : ''; - if (shaderType === "vertex") { + if (shaderType === 'vertex') { // Emit flat out declaration and inject assignment into main() body hooks += `${qualifier}OUT ${declaration};\n`; - postMain = postMain.replace(/\{/, `{\n ${declaration.split(' ').pop()} = ${source};`); - } else if (shaderType === "fragment") { + postMain = postMain.replace( + /\{/, + `{\n ${declaration.split(' ').pop()} = ${source};` + ); + } else if (shaderType === 'fragment') { hooks += `${qualifier}IN ${declaration};\n`; } } @@ -452,8 +459,8 @@ export function populateGLSLHooks(shader, src, shaderType) { hooks += `${hookDef}${shader.hooks.helpers[hookDef]}\n`; } for (const hookDef in shader.hooks[shaderType]) { - if (hookDef === "declarations") continue; - const [hookType, hookName] = hookDef.split(" "); + if (hookDef === 'declarations') continue; + const [hookType, hookName] = hookDef.split(' '); // Add a #define so that if the shader wants to use preprocessor directives to // optimize away the extra function calls in main, it can do so @@ -461,43 +468,43 @@ export function populateGLSLHooks(shader, src, shaderType) { shader.hooks.modified.vertex[hookDef] || shader.hooks.modified.fragment[hookDef] ) { - defines += "#define AUGMENTED_HOOK_" + hookName + "\n"; + defines += '#define AUGMENTED_HOOK_' + hookName + '\n'; } hooks += - hookType + " HOOK_" + hookName + shader.hooks[shaderType][hookDef] + "\n"; + hookType + ' HOOK_' + hookName + shader.hooks[shaderType][hookDef] + '\n'; } // Allow shaders to specify the location of hook #define statements. Normally these // go after function definitions, but one might want to have them defined earlier // in order to only conditionally make uniforms. - if (preMain.indexOf("#define HOOK_DEFINES") !== -1) { - preMain = preMain.replace("#define HOOK_DEFINES", "\n" + defines + "\n"); - defines = ""; + if (preMain.indexOf('#define HOOK_DEFINES') !== -1) { + preMain = preMain.replace('#define HOOK_DEFINES', '\n' + defines + '\n'); + defines = ''; } - return preMain + "\n" + defines + hooks + main + postMain; + return preMain + '\n' + defines + hooks + main + postMain; } export function checkWebGLCapabilities({ GL, webglVersion }) { const gl = GL; const supportsFloat = webglVersion === constants.WEBGL2 - ? gl.getExtension("EXT_color_buffer_float") && - gl.getExtension("EXT_float_blend") - : gl.getExtension("OES_texture_float"); + ? gl.getExtension('EXT_color_buffer_float') && + gl.getExtension('EXT_float_blend') + : gl.getExtension('OES_texture_float'); const supportsFloatLinear = - supportsFloat && gl.getExtension("OES_texture_float_linear"); + supportsFloat && gl.getExtension('OES_texture_float_linear'); const supportsHalfFloat = webglVersion === constants.WEBGL2 - ? gl.getExtension("EXT_color_buffer_float") - : gl.getExtension("OES_texture_half_float"); + ? gl.getExtension('EXT_color_buffer_float') + : gl.getExtension('OES_texture_half_float'); const supportsHalfFloatLinear = - supportsHalfFloat && gl.getExtension("OES_texture_half_float_linear"); + supportsHalfFloat && gl.getExtension('OES_texture_half_float_linear'); return { float: supportsFloat, floatLinear: supportsFloatLinear, halfFloat: supportsHalfFloat, - halfFloatLinear: supportsHalfFloatLinear, + halfFloatLinear: supportsHalfFloatLinear }; } diff --git a/src/webgpu/index.js b/src/webgpu/index.js index fb7670482e..1865aac965 100644 --- a/src/webgpu/index.js +++ b/src/webgpu/index.js @@ -1,5 +1,5 @@ import rendererWebGPU from './p5.RendererWebGPU'; -export default function(p5){ +export default function (p5) { p5.registerAddon(rendererWebGPU); } diff --git a/src/webgpu/p5.RendererWebGPU.js b/src/webgpu/p5.RendererWebGPU.js index 630f0ed7a7..e3b94a4c1d 100644 --- a/src/webgpu/p5.RendererWebGPU.js +++ b/src/webgpu/p5.RendererWebGPU.js @@ -9,14 +9,24 @@ import { getStrokeDefs } from '../webgl/enums'; import { DataType, INSTANCE_ID_VARYING_NAME } from '../strands/ir_types.js'; import { colorVertexShader, colorFragmentShader } from './shaders/color'; -import { lineVertexShader, lineFragmentShader} from './shaders/line'; -import { materialVertexShader, materialFragmentShader } from './shaders/material'; +import { lineVertexShader, lineFragmentShader } from './shaders/line'; +import { + materialVertexShader, + materialFragmentShader +} from './shaders/material'; import { fontVertexShader, fontFragmentShader } from './shaders/font'; import { blitVertexShader, blitFragmentShader } from './shaders/blit'; import { wgslBackend } from './strands_wgslBackend'; -import { baseFilterVertexShader, baseFilterFragmentShader } from './shaders/filters/base'; -import { imageLightVertexShader, imageLightDiffusedFragmentShader, imageLightSpecularFragmentShader } from './shaders/imageLight'; +import { + baseFilterVertexShader, + baseFilterFragmentShader +} from './shaders/filters/base'; +import { + imageLightVertexShader, + imageLightDiffusedFragmentShader, + imageLightSpecularFragmentShader +} from './shaders/imageLight'; import { baseComputeShader } from './shaders/compute'; const FRAME_STATE = { @@ -30,15 +40,8 @@ function rendererWebGPU(p5, fn) { // RendererWebGPU depends on these other classes being set up prior, // as it is optimized for being in a standalone build, not core - const { - Renderer3D, - Shader, - Texture, - MipmapTexture, - Image, - Camera, - RGBA, - } = p5; + const { Renderer3D, Shader, Texture, MipmapTexture, Image, Camera, RGBA } = + p5; class StorageBuffer { constructor(buffer, size, renderer, schema = null) { @@ -141,8 +144,8 @@ function rendererWebGPU(p5, fn) { if (newSchema.structBody !== this._schema.structBody) { throw new Error( `update() data structure doesn't match the original.\n` + - ` Expected: ${this._schema.structBody}\n` + - ` Got: ${newSchema.structBody}` + ` Expected: ${this._schema.structBody}\n` + + ` Got: ${newSchema.structBody}` ); } @@ -224,11 +227,17 @@ function rendererWebGPU(p5, fn) { const stagingBuffer = device.createBuffer({ size: this.size, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }); const commandEncoder = device.createCommandEncoder(); - commandEncoder.copyBufferToBuffer(this.buffer, 0, stagingBuffer, 0, this.size); + commandEncoder.copyBufferToBuffer( + this.buffer, + 0, + stagingBuffer, + 0, + this.size + ); device.queue.submit([commandEncoder.finish()]); await stagingBuffer.mapAsync(GPUMapMode.READ, 0, this.size); @@ -316,7 +325,11 @@ function rendererWebGPU(p5, fn) { if (this._schema !== null) { // buffer was created with an array of structs - if (typeof value !== 'object' || value === null || Array.isArray(value)) { + if ( + typeof value !== 'object' || + value === null || + Array.isArray(value) + ) { throw new Error( 'set() expects a plain object matching the original struct format for this buffer' ); @@ -328,20 +341,24 @@ function rendererWebGPU(p5, fn) { if (byteOffset + stride > this.size) { throw new Error( `set() index ${index} is out of bounds for this buffer ` + - `(buffer holds ${Math.floor(this.size / stride)} elements)` + `(buffer holds ${Math.floor(this.size / stride)} elements)` ); } // pack just this one element using the same logic as update() const packed = this._renderer._packStructArray([value], this._schema); // use packed.buffer (ArrayBuffer) so the size arg is always in bytes - device.queue.writeBuffer(this.buffer, byteOffset, packed.buffer, 0, stride); + device.queue.writeBuffer( + this.buffer, + byteOffset, + packed.buffer, + 0, + stride + ); } else { // buffer was created with a float array if (typeof value !== 'number') { - throw new Error( - 'set() expects a number for this float buffer' - ); + throw new Error('set() expects a number for this float buffer'); } const byteOffset = index * 4; @@ -349,11 +366,15 @@ function rendererWebGPU(p5, fn) { if (byteOffset + 4 > this.size) { throw new Error( `set() index ${index} is out of bounds for this buffer ` + - `(buffer holds ${Math.floor(this.size / 4)} floats)` + `(buffer holds ${Math.floor(this.size / 4)} floats)` ); } - device.queue.writeBuffer(this.buffer, byteOffset, new Float32Array([value])); + device.queue.writeBuffer( + this.buffer, + byteOffset, + new Float32Array([value]) + ); } } } @@ -374,7 +395,7 @@ function rendererWebGPU(p5, fn) { class RendererWebGPU extends Renderer3D { constructor(pInst, w, h, isMainCanvas, elt) { - super(pInst, w, h, isMainCanvas, elt) + super(pInst, w, h, isMainCanvas, elt); // Used to group draws into one big render pass this.activeRenderPass = null; @@ -449,18 +470,23 @@ function rendererWebGPU(p5, fn) { _setAttributeDefaults(pInst) { const defaults = { forceFallbackAdapter: false, - powerPreference: 'high-performance', + powerPreference: 'high-performance' }; if (pInst._webgpuAttributes === null) { pInst._webgpuAttributes = defaults; } else { - pInst._webgpuAttributes = Object.assign(defaults, pInst._webgpuAttributes); + pInst._webgpuAttributes = Object.assign( + defaults, + pInst._webgpuAttributes + ); } return; } async _initContext() { - this.adapter = await navigator.gpu?.requestAdapter(this._webgpuAttributes); + this.adapter = await navigator.gpu?.requestAdapter( + this._webgpuAttributes + ); this.device = await this.adapter?.requestDevice({ // Todo: check support requiredFeatures: ['depth32float-stencil8'] @@ -475,7 +501,7 @@ function rendererWebGPU(p5, fn) { device: this.device, format: this.presentationFormat, usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, - alphaMode: 'premultiplied', + alphaMode: 'premultiplied' }); // TODO disablable stencil @@ -486,16 +512,16 @@ function rendererWebGPU(p5, fn) { } async _setAttributes(key, value) { - if (typeof this._pInst._webgpuAttributes === "undefined") { + if (typeof this._pInst._webgpuAttributes === 'undefined') { console.log( - "You are trying to use setAttributes on a p5.Graphics object " + - "that does not use a WebGPU renderer." + 'You are trying to use setAttributes on a p5.Graphics object ' + + 'that does not use a WebGPU renderer.' ); return; } let unchanged = true; - if (typeof value !== "undefined") { + if (typeof value !== 'undefined') { //first time modifying the attributes if (this._pInst._webgpuAttributes === null) { this._pInst._webgpuAttributes = {}; @@ -519,7 +545,8 @@ function rendererWebGPU(p5, fn) { if (!this._pInst._setupDone) { if (this.geometryBufferCache.numCached() > 0) { - p5.FES.log`Sorry, Could not set the attributes, you need to call setAttributes() before calling the other drawing methods in setup()`(); + p5.FES + .log`Sorry, Could not set the attributes, you need to call setAttributes() before calling the other drawing methods in setup()`(); return; } } @@ -543,10 +570,10 @@ function rendererWebGPU(p5, fn) { size: { width: Math.ceil(this.width * this._pixelDensity), height: Math.ceil(this.height * this._pixelDensity), - depthOrArrayLayers: 1, + depthOrArrayLayers: 1 }, format: this.depthFormat, - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC }); this.depthTextureView = this.depthTexture.createView(); @@ -572,38 +599,39 @@ function rendererWebGPU(p5, fn) { const colorAttachment = { view: activeFramebuffer - ? (activeFramebuffer.aaColorTexture - ? activeFramebuffer.aaColorTextureView - : activeFramebuffer.colorTextureView) + ? activeFramebuffer.aaColorTexture + ? activeFramebuffer.aaColorTextureView + : activeFramebuffer.colorTextureView : this._getCanvasColorTextureView(), - loadOp: "load", - storeOp: "store", + loadOp: 'load', + storeOp: 'store', // If using multisampled texture, resolve to non-multisampled texture - resolveTarget: activeFramebuffer && activeFramebuffer.aaColorTexture - ? activeFramebuffer.colorTextureView - : undefined, + resolveTarget: + activeFramebuffer && activeFramebuffer.aaColorTexture + ? activeFramebuffer.colorTextureView + : undefined }; // Use framebuffer depth texture if active, otherwise use canvas depth texture const depthTextureView = activeFramebuffer - ? (activeFramebuffer.aaDepthTexture - ? activeFramebuffer.aaDepthTextureView - : activeFramebuffer.depthTextureView) + ? activeFramebuffer.aaDepthTexture + ? activeFramebuffer.aaDepthTextureView + : activeFramebuffer.depthTextureView : this.depthTextureView; const renderPassDescriptor = { colorAttachments: [colorAttachment], depthStencilAttachment: depthTextureView ? { view: depthTextureView, - depthLoadOp: "load", - depthStoreOp: "store", + depthLoadOp: 'load', + depthStoreOp: 'store', depthClearValue: 1.0, - stencilLoadOp: "load", - stencilStoreOp: "store", + stencilLoadOp: 'load', + stencilStoreOp: 'store', depthReadOnly: false, - stencilReadOnly: false, + stencilReadOnly: false } - : undefined, + : undefined }; const commandEncoder = this.device.createCommandEncoder(); const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); @@ -641,7 +669,10 @@ function rendererWebGPU(p5, fn) { // Note: a mid-draw-loop transition from UNPROMOTED back to PROMOTED // (i.e. calling background() some frames but not others) will still // lose intermediate UNPROMOTED frame content. - if (this._frameState !== FRAME_STATE.PROMOTED && !this.activeFramebuffer()) { + if ( + this._frameState !== FRAME_STATE.PROMOTED && + !this.activeFramebuffer() + ) { if (this._pInst.frameCount > 0) { this._frameState = FRAME_STATE.UNPROMOTED; } else { @@ -659,39 +690,40 @@ function rendererWebGPU(p5, fn) { const colorAttachment = { view: activeFramebuffer - ? (activeFramebuffer.aaColorTexture - ? activeFramebuffer.aaColorTextureView - : activeFramebuffer.colorTextureView) + ? activeFramebuffer.aaColorTexture + ? activeFramebuffer.aaColorTextureView + : activeFramebuffer.colorTextureView : this._getCanvasColorTextureView(), clearValue: { r: _r * _a, g: _g * _a, b: _b * _a, a: _a }, loadOp: 'clear', storeOp: 'store', // If using multisampled texture, resolve to non-multisampled texture - resolveTarget: activeFramebuffer && activeFramebuffer.aaColorTexture - ? activeFramebuffer.colorTextureView - : undefined, + resolveTarget: + activeFramebuffer && activeFramebuffer.aaColorTexture + ? activeFramebuffer.colorTextureView + : undefined }; // Use framebuffer depth texture if active, otherwise use canvas depth texture const depthTextureView = activeFramebuffer - ? (activeFramebuffer.aaDepthTexture - ? activeFramebuffer.aaDepthTextureView - : activeFramebuffer.depthTextureView) + ? activeFramebuffer.aaDepthTexture + ? activeFramebuffer.aaDepthTextureView + : activeFramebuffer.depthTextureView : this.depthTextureView; const depthAttachment = depthTextureView ? { - view: depthTextureView, - depthClearValue: 1.0, - depthLoadOp: 'clear', - depthStoreOp: 'store', - stencilLoadOp: 'load', - stencilStoreOp: 'store', - } + view: depthTextureView, + depthClearValue: 1.0, + depthLoadOp: 'clear', + depthStoreOp: 'store', + stencilLoadOp: 'load', + stencilStoreOp: 'store' + } : undefined; const renderPassDescriptor = { colorAttachments: [colorAttachment], - ...(depthAttachment ? { depthStencilAttachment: depthAttachment } : {}), + ...(depthAttachment ? { depthStencilAttachment: depthAttachment } : {}) }; const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); @@ -715,9 +747,9 @@ function rendererWebGPU(p5, fn) { // Use framebuffer depth texture if active, otherwise use canvas depth texture const depthTextureView = activeFramebuffer - ? (activeFramebuffer.aaDepthTexture - ? activeFramebuffer.aaDepthTextureView - : activeFramebuffer.depthTextureView) + ? activeFramebuffer.aaDepthTexture + ? activeFramebuffer.aaDepthTextureView + : activeFramebuffer.depthTextureView : this.depthTextureView; if (!depthTextureView) { @@ -731,12 +763,12 @@ function rendererWebGPU(p5, fn) { depthLoadOp: 'clear', depthStoreOp: 'store', stencilLoadOp: 'load', - stencilStoreOp: 'store', + stencilStoreOp: 'store' }; const renderPassDescriptor = { colorAttachments: [], // No color attachments, we're only clearing depth - depthStencilAttachment: depthAttachment, + depthStencilAttachment: depthAttachment }; const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); @@ -774,7 +806,11 @@ function rendererWebGPU(p5, fn) { const typed = this._normalizeBufferData(raw, Float32Array); // Get pooled buffer (may reuse existing or create new) - const pooledBufferInfo = this._getVertexBufferFromPool(geometry, dst, typed.byteLength); + const pooledBufferInfo = this._getVertexBufferFromPool( + geometry, + dst, + typed.byteLength + ); // Create a copy of the data to avoid conflicts when geometry arrays are reset const dataCopy = new typed.constructor(typed); @@ -810,7 +846,7 @@ function rendererWebGPU(p5, fn) { const buffer = device.createBuffer({ size: Math.ceil((indices.length * indexType.BYTES_PER_ELEMENT) / 4) * 4, usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, - mappedAtCreation: true, + mappedAtCreation: true }); // Write index data to buffer @@ -823,7 +859,7 @@ function rendererWebGPU(p5, fn) { } _freeBuffers(buffers) { - const destroyIfExists = (buf) => { + const destroyIfExists = buf => { if (buf && buf.destroy) { buf.destroy(); } @@ -831,7 +867,7 @@ function rendererWebGPU(p5, fn) { destroyIfExists(buffers.indexBuffer); - const freeDefs = (defs) => { + const freeDefs = defs => { for (const def of defs) { destroyIfExists(buffers[def.dst]); buffers[def.dst] = null; @@ -853,17 +889,21 @@ function rendererWebGPU(p5, fn) { _shaderOptions({ mode, compute, workgroupSize }) { if (compute) return { compute: true, workgroupSize }; const activeFramebuffer = this.activeFramebuffer(); - const format = activeFramebuffer ? - this._getWebGPUColorFormat(activeFramebuffer) : - this.presentationFormat; - - const requestedSampleCount = activeFramebuffer ? - (activeFramebuffer.antialias ? activeFramebuffer.antialiasSamples : 1) : - 1; // No MSAA needed when blitting already-antialiased textures to canvas + const format = activeFramebuffer + ? this._getWebGPUColorFormat(activeFramebuffer) + : this.presentationFormat; + + const requestedSampleCount = activeFramebuffer + ? activeFramebuffer.antialias + ? activeFramebuffer.antialiasSamples + : 1 + : 1; // No MSAA needed when blitting already-antialiased textures to canvas const sampleCount = this._getValidSampleCount(requestedSampleCount); const depthFormat = activeFramebuffer - ? (activeFramebuffer.useDepth ? this._getWebGPUDepthFormat(activeFramebuffer) : undefined) + ? activeFramebuffer.useDepth + ? this._getWebGPUDepthFormat(activeFramebuffer) + : undefined : this.depthFormat; const drawTarget = this.drawTarget(); @@ -871,14 +911,17 @@ function rendererWebGPU(p5, fn) { const clipApplied = drawTarget._isClipApplied; return { - topology: mode === constants.TRIANGLE_STRIP ? 'triangle-strip' : 'triangle-list', + topology: + mode === constants.TRIANGLE_STRIP + ? 'triangle-strip' + : 'triangle-list', blendMode: this.states.curBlendMode, sampleCount, format, depthFormat, clipping, - clipApplied, - } + clipApplied + }; } _shaderOptionsDifferent(newOptions) { @@ -894,7 +937,9 @@ function rendererWebGPU(p5, fn) { if (shader.shaderType === 'compute') { // Compute shader initialization - shader.computeModule = device.createShaderModule({ code: shader.computeSrc() }); + shader.computeModule = device.createShaderModule({ + code: shader.computeSrc() + }); shader._computePipelineCache = null; shader._workgroupSize = null; @@ -921,7 +966,15 @@ function rendererWebGPU(p5, fn) { shader.fragModule = device.createShaderModule({ code: shader.fragSrc() }); shader._pipelineCache = new Map(); - shader.getPipeline = ({ topology, blendMode, sampleCount, format, depthFormat, clipping, clipApplied }) => { + shader.getPipeline = ({ + topology, + blendMode, + sampleCount, + format, + depthFormat, + clipping, + clipApplied + }) => { const key = `${topology}_${blendMode}_${sampleCount}_${format}_${depthFormat}_${clipping}_${clipApplied}`; if (!shader._pipelineCache.has(key)) { const pipeline = device.createRenderPipeline({ @@ -929,44 +982,56 @@ function rendererWebGPU(p5, fn) { vertex: { module: shader.vertModule, entryPoint: 'main', - buffers: this._getVertexLayout(shader), + buffers: this._getVertexLayout(shader) }, fragment: { module: shader.fragModule, entryPoint: 'main', - targets: [{ - format, - blend: this._getBlendState(blendMode), - }], + targets: [ + { + format, + blend: this._getBlendState(blendMode) + } + ] }, primitive: { topology }, multisample: { count: sampleCount }, - ...(depthFormat ? { - depthStencil: { - format: depthFormat, - depthWriteEnabled: !clipping, - depthCompare: 'less-equal', - stencilFront: { - compare: clipping ? 'always' : (clipApplied ? 'not-equal' : 'always'), - failOp: 'keep', - depthFailOp: 'keep', - passOp: clipping ? 'replace' : 'keep', - }, - stencilBack: { - compare: clipping ? 'always' : (clipApplied ? 'not-equal' : 'always'), - failOp: 'keep', - depthFailOp: 'keep', - passOp: clipping ? 'replace' : 'keep', - }, - stencilReadMask: 0xFF, - stencilWriteMask: clipping ? 0xFF : 0x00, - }, - } : {}), + ...(depthFormat + ? { + depthStencil: { + format: depthFormat, + depthWriteEnabled: !clipping, + depthCompare: 'less-equal', + stencilFront: { + compare: clipping + ? 'always' + : clipApplied + ? 'not-equal' + : 'always', + failOp: 'keep', + depthFailOp: 'keep', + passOp: clipping ? 'replace' : 'keep' + }, + stencilBack: { + compare: clipping + ? 'always' + : clipApplied + ? 'not-equal' + : 'always', + failOp: 'keep', + depthFailOp: 'keep', + passOp: clipping ? 'replace' : 'keep' + }, + stencilReadMask: 0xff, + stencilWriteMask: clipping ? 0xff : 0x00 + } + } + : {}) }); shader._pipelineCache.set(key, pipeline); } return shader._pipelineCache.get(key); - } + }; } _finalizeShader(shader) { @@ -980,10 +1045,7 @@ function rendererWebGPU(p5, fn) { for (const group of shader._uniformGroups) { // Calculate the size needed for this group's uniforms const groupUniforms = Object.values(group.uniforms); - const rawSize = Math.max( - 0, - ...groupUniforms.map(u => u.offsetEnd) - ); + const rawSize = Math.max(0, ...groupUniforms.map(u => u.offsetEnd)); const alignedSize = Math.ceil(rawSize / 16) * 16; shader._uniformBufferGroups.push({ @@ -1000,7 +1062,7 @@ function rendererWebGPU(p5, fn) { dynamic: groupUniforms.some(u => u.name.startsWith('uModel')), buffersInUse: new Set(), - currentBuffer: null, // For caching + currentBuffer: null // For caching }); } @@ -1017,10 +1079,11 @@ function rendererWebGPU(p5, fn) { entries.push({ bufferGroup, binding: bufferGroup.binding, - visibility: shader.shaderType === 'compute' - ? GPUShaderStage.COMPUTE - : GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, - buffer: { type: 'uniform', hasDynamicOffset: bufferGroup.dynamic }, + visibility: + shader.shaderType === 'compute' + ? GPUShaderStage.COMPUTE + : GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, + buffer: { type: 'uniform', hasDynamicOffset: bufferGroup.dynamic } }); structEntries.set(bufferGroup.group, entries); } @@ -1040,13 +1103,13 @@ function rendererWebGPU(p5, fn) { entries.push({ binding: sampler.binding, visibility: sampler.visibility, - sampler: sampler.type === 'sampler' - ? { type: 'filtering' } - : undefined, - texture: sampler.type === 'texture_2d' - ? { sampleType: 'float', viewDimension: '2d' } - : undefined, - uniform: sampler, + sampler: + sampler.type === 'sampler' ? { type: 'filtering' } : undefined, + texture: + sampler.type === 'texture_2d' + ? { sampleType: 'float', viewDimension: '2d' } + : undefined, + uniform: sampler }); entries.sort((a, b) => a.binding - b.binding); @@ -1062,9 +1125,10 @@ function rendererWebGPU(p5, fn) { binding: storage.binding, visibility: storage.visibility, buffer: { - type: storage.accessMode === 'read' ? 'read-only-storage' : 'storage' + type: + storage.accessMode === 'read' ? 'read-only-storage' : 'storage' }, - storage: storage, + storage: storage }); entries.sort((a, b) => a.binding - b.binding); @@ -1087,7 +1151,7 @@ function rendererWebGPU(p5, fn) { // possibly cache bind groups if unchanged shader._lastDynamicBuffer = {}; shader._pipelineLayout = this.device.createPipelineLayout({ - bindGroupLayouts: shader._bindGroupLayouts, + bindGroupLayouts: shader._bindGroupLayouts }); shader._compiled = true; } @@ -1266,9 +1330,9 @@ function rendererWebGPU(p5, fn) { { shaderLocation: attr.location, offset: 0, - format, - }, - ], + format + } + ] }); } return layouts; @@ -1284,8 +1348,9 @@ function rendererWebGPU(p5, fn) { // Get the vertex buffer info associated with this attribute const renderBuffer = - this.buffers[shader.shaderType].find(buf => buf.attr === attrName) || - this.buffers.user.find(buf => buf.attr === attrName); + this.buffers[shader.shaderType].find( + buf => buf.attr === attrName + ) || this.buffers.user.find(buf => buf.attr === attrName); if (!renderBuffer) continue; buffers.push(renderBuffer); @@ -1298,11 +1363,16 @@ function rendererWebGPU(p5, fn) { _getFormatFromSize(size) { switch (size) { - case 1: return 'float32'; - case 2: return 'float32x2'; - case 3: return 'float32x3'; - case 4: return 'float32x4'; - default: throw new Error(`Unsupported attribute size: ${size}`); + case 1: + return 'float32'; + case 2: + return 'float32x2'; + case 3: + return 'float32x3'; + case 4: + return 'float32x4'; + default: + throw new Error(`Unsupported attribute size: ${size}`); } } @@ -1311,7 +1381,7 @@ function rendererWebGPU(p5, fn) { _updateViewport() { this._origViewport = { width: this.width, - height: this.height, + height: this.height }; this._viewport = [0, 0, this.width, this.height]; } @@ -1351,9 +1421,9 @@ function rendererWebGPU(p5, fn) { const commandEncoder = this.device.createCommandEncoder(); const depthTextureView = activeFramebuffer - ? (activeFramebuffer.aaDepthTexture - ? activeFramebuffer.aaDepthTextureView - : activeFramebuffer.depthTextureView) + ? activeFramebuffer.aaDepthTexture + ? activeFramebuffer.aaDepthTextureView + : activeFramebuffer.depthTextureView : this.depthTextureView; if (depthTextureView) { @@ -1363,13 +1433,14 @@ function rendererWebGPU(p5, fn) { depthLoadOp: 'clear', depthStoreOp: 'store', stencilLoadOp: 'load', - stencilStoreOp: 'store', + stencilStoreOp: 'store' }; const renderPassDescriptor = { colorAttachments: [], - depthStencilAttachment: depthAttachment, + depthStencilAttachment: depthAttachment }; - const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); + const passEncoder = + commandEncoder.beginRenderPass(renderPassDescriptor); passEncoder.end(); this._pendingCommandEncoders.push(commandEncoder.finish()); this._hasPendingDraws = true; @@ -1402,8 +1473,10 @@ function rendererWebGPU(p5, fn) { const canvasTexture = this.drawingContext.getCurrentTexture(); // Ensure mainFramebuffer matches canvas size - if (this.mainFramebuffer.width !== this.width || - this.mainFramebuffer.height !== this.height) { + if ( + this.mainFramebuffer.width !== this.width || + this.mainFramebuffer.height !== this.height + ) { this.mainFramebuffer.resize(this.width, this.height); } @@ -1415,17 +1488,17 @@ function rendererWebGPU(p5, fn) { { texture: canvasTexture, origin: { x: 0, y: 0, z: 0 }, - mipLevel: 0, + mipLevel: 0 }, { texture: this.mainFramebuffer.colorTexture, origin: { x: 0, y: 0, z: 0 }, - mipLevel: 0, + mipLevel: 0 }, { width: Math.ceil(this.width * this._pixelDensity), height: Math.ceil(this.height * this._pixelDensity), - depthOrArrayLayers: 1, + depthOrArrayLayers: 1 } ); @@ -1434,17 +1507,17 @@ function rendererWebGPU(p5, fn) { { texture: this.depthTexture, origin: { x: 0, y: 0, z: 0 }, - mipLevel: 0, + mipLevel: 0 }, { texture: this.mainFramebuffer.depthTexture, origin: { x: 0, y: 0, z: 0 }, - mipLevel: 0, + mipLevel: 0 }, { width: Math.ceil(this.width * this._pixelDensity), height: Math.ceil(this.height * this._pixelDensity), - depthOrArrayLayers: 1, + depthOrArrayLayers: 1 } ); @@ -1471,8 +1544,10 @@ function rendererWebGPU(p5, fn) { } // Ensure mainFramebuffer matches canvas size - if (this.mainFramebuffer.width !== this.width || - this.mainFramebuffer.height !== this.height) { + if ( + this.mainFramebuffer.width !== this.width || + this.mainFramebuffer.height !== this.height + ) { this.mainFramebuffer.resize(this.width, this.height); } @@ -1544,7 +1619,7 @@ function rendererWebGPU(p5, fn) { // No suitable buffer available, create a new one const newBuffer = this.device.createBuffer({ size, - usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, + usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST }); const bufferInfo = { @@ -1562,7 +1637,9 @@ function rendererWebGPU(p5, fn) { // Return buffers marked for return back to their pools for all registered geometries for (const geometry of this._geometriesWithPools) { if (geometry._vertexBuffersToReturn) { - for (const [dst, buffersToReturn] of Object.entries(geometry._vertexBuffersToReturn)) { + for (const [dst, buffersToReturn] of Object.entries( + geometry._vertexBuffersToReturn + )) { if (buffersToReturn.length > 0) { // Move all buffers from ToReturn back to pool const pool = geometry._vertexBufferPools[dst] || []; @@ -1587,7 +1664,9 @@ function rendererWebGPU(p5, fn) { // Mark geometry buffers for return when geometry is reset/freed _markGeometryBuffersForReturn(geometry) { if (geometry._vertexBuffersInUse && geometry._vertexBuffersToReturn) { - for (const [dst, buffersInUse] of Object.entries(geometry._vertexBuffersInUse)) { + for (const [dst, buffersInUse] of Object.entries( + geometry._vertexBuffersInUse + )) { if (buffersInUse.length > 0) { // Move all buffers from InUse to ToReturn const buffersToReturn = geometry._vertexBuffersToReturn[dst] || []; @@ -1616,7 +1695,7 @@ function rendererWebGPU(p5, fn) { // No buffers available, create a new one const newBuffer = this.device.createBuffer({ size: bufferGroup.size, - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }); const newData = new Float32Array(bufferGroup.size / 4); const newDataView = new DataView(newData.buffer); @@ -1635,7 +1714,8 @@ function rendererWebGPU(p5, fn) { let buffer; if ( this.currentUniformBuffer && - this.currentUniformBuffer.offset + bufferGroup.size < this.currentUniformBuffer.size + this.currentUniformBuffer.offset + bufferGroup.size < + this.currentUniformBuffer.size ) { // We can fit this next block of uniforms into the current active memory chunk buffer = this.currentUniformBuffer; @@ -1658,13 +1738,13 @@ function rendererWebGPU(p5, fn) { buffer: this.device.createBuffer({ size, usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC, - mappedAtCreation: true, + mappedAtCreation: true }), uniformBuffer: this.device.createBuffer({ size, - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, - }), - } + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + }) + }; buffer.data = new Float32Array(buffer.buffer.getMappedRange()); buffer.dataView = new DataView(buffer.data.buffer); @@ -1718,7 +1798,7 @@ function rendererWebGPU(p5, fn) { bufferInfo.buffer.unmap(); encoder.copyBufferToBuffer( bufferInfo.buffer, - bufferInfo.uniformBuffer, + bufferInfo.uniformBuffer ); } commandsToSubmit.unshift(encoder.finish()); @@ -1729,19 +1809,19 @@ function rendererWebGPU(p5, fn) { for (const buf of this.activeUniformBuffers) { // buf.buffer = this.device.createBuffer({ - // size: buf.size, - // usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC, - // mappedAtCreation: true, + // size: buf.size, + // usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC, + // mappedAtCreation: true, // }); buf.offset = 0; buf.lastOffset = 0; // this.resettingUniformBuffers.push( - buf.buffer.mapAsync(GPUMapMode.WRITE).then(() => { - buf.data = new Float32Array(buf.buffer.getMappedRange()); - buf.dataView = new DataView(buf.data.buffer); - this.uniformBufferPool.push(buf); - return buf; - }) + buf.buffer.mapAsync(GPUMapMode.WRITE).then(() => { + buf.data = new Float32Array(buf.buffer.getMappedRange()); + buf.dataView = new DataView(buf.data.buffer); + this.uniformBufferPool.push(buf); + return buf; + }); // ) } this.activeUniformBuffers = []; @@ -1772,7 +1852,10 @@ function rendererWebGPU(p5, fn) { } // Resize canvas if dimensions changed - if (this._pixelReadCanvas.width !== width || this._pixelReadCanvas.height !== height) { + if ( + this._pixelReadCanvas.width !== width || + this._pixelReadCanvas.height !== height + ) { this._pixelReadCanvas.width = width; this._pixelReadCanvas.height = height; } @@ -1838,7 +1921,10 @@ function rendererWebGPU(p5, fn) { if (this._frameState === FRAME_STATE.PROMOTED) { for (const { fbo, diff } of states) { - if (fbo !== this.mainFramebuffer || this._frameState !== FRAME_STATE.PROMOTED) { + if ( + fbo !== this.mainFramebuffer || + this._frameState !== FRAME_STATE.PROMOTED + ) { fbo.begin(); } for (const key in diff) { @@ -1857,7 +1943,10 @@ function rendererWebGPU(p5, fn) { if (!buffers) return; // If PENDING and no custom framebuffer, regular draw means PROMOTE - if (this._frameState === FRAME_STATE.PENDING && !this.activeFramebuffer()) { + if ( + this._frameState === FRAME_STATE.PENDING && + !this.activeFramebuffer() + ) { this._promoteToFramebufferWithoutCopy(); } @@ -1867,31 +1956,32 @@ function rendererWebGPU(p5, fn) { const currentShader = this._curShader; this.setupShaderBindGroups(currentShader, passEncoder, { mode, buffers }); // Bind vertex buffers - for (const buffer of currentShader._vertexBuffers || this._getVertexBuffers(currentShader)) { + for (const buffer of currentShader._vertexBuffers || + this._getVertexBuffers(currentShader)) { const location = currentShader.attributes[buffer.attr].location; const gpuBuffer = buffers[buffer.dst]; passEncoder.setVertexBuffer(location, gpuBuffer, 0); } - if (currentShader.shaderType === "fill") { + if (currentShader.shaderType === 'fill') { // Bind index buffer and issue draw if (buffers.indexBuffer) { - const indexFormat = buffers.indexFormat || "uint16"; + const indexFormat = buffers.indexFormat || 'uint16'; passEncoder.setIndexBuffer(buffers.indexBuffer, indexFormat); passEncoder.drawIndexed(geometry.faces.length * 3, count, 0, 0, 0); } else { passEncoder.draw(geometry.vertices.length, count, 0, 0); } - } else if (currentShader.shaderType === "text") { + } else if (currentShader.shaderType === 'text') { if (!buffers.indexBuffer) { - throw new Error("Text geometry must have an index buffer"); + throw new Error('Text geometry must have an index buffer'); } - const indexFormat = buffers.indexFormat || "uint16"; + const indexFormat = buffers.indexFormat || 'uint16'; passEncoder.setIndexBuffer(buffers.indexBuffer, indexFormat); passEncoder.drawIndexed(geometry.faces.length * 3, count, 0, 0, 0); } - if (buffers.lineVerticesBuffer && currentShader.shaderType === "stroke") { + if (buffers.lineVerticesBuffer && currentShader.shaderType === 'stroke') { passEncoder.draw(geometry.lineVertices.length / 3, count, 0, 0); } @@ -1929,14 +2019,25 @@ function rendererWebGPU(p5, fn) { if (bufferGroup.dynamic) { // Bind uniforms into a part of a big dynamic memory block because // the group changes often - const uniformBufferInfo = this._getDynamicUniformBufferFromPool(bufferGroup); - if (currentShader._lastDynamicBuffer[bufferGroup.cacheKey] !== uniformBufferInfo) { + const uniformBufferInfo = + this._getDynamicUniformBufferFromPool(bufferGroup); + if ( + currentShader._lastDynamicBuffer[bufferGroup.cacheKey] !== + uniformBufferInfo + ) { currentShader._cachedBindGroup[bufferGroup.group] = undefined; - currentShader._lastDynamicBuffer[bufferGroup.cacheKey] = uniformBufferInfo; + currentShader._lastDynamicBuffer[bufferGroup.cacheKey] = + uniformBufferInfo; } - this._packUniformGroup(currentShader, bufferGroup.uniforms, uniformBufferInfo); + this._packUniformGroup( + currentShader, + bufferGroup.uniforms, + uniformBufferInfo + ); uniformBufferInfo.lastOffset = uniformBufferInfo.offset; - uniformBufferInfo.offset += Math.ceil(bufferGroup.size / this.uniformBufferAlignment) * this.uniformBufferAlignment; + uniformBufferInfo.offset += + Math.ceil(bufferGroup.size / this.uniformBufferAlignment) * + this.uniformBufferAlignment; // Make a shallow copy so that we keep track of the last offset for this uniform bufferGroup.currentDynamicBuffer = uniformBufferInfo; @@ -1944,7 +2045,10 @@ function rendererWebGPU(p5, fn) { } else { // Bind uniforms to a binding-specific buffer, which may be cached for performance let bufferInfo; - const dataChanged = this._hasGroupDataChanged(currentShader, bufferGroup); + const dataChanged = this._hasGroupDataChanged( + currentShader, + bufferGroup + ); if (!dataChanged && bufferGroup.currentBuffer) { // Reuse the cached buffer - no need to pack or write @@ -1953,7 +2057,11 @@ function rendererWebGPU(p5, fn) { } else { // Data changed - get a new buffer and write to it bufferInfo = this._getUniformBufferFromPool(bufferGroup); - this._packUniformGroup(currentShader, bufferGroup.uniforms, bufferInfo); + this._packUniformGroup( + currentShader, + bufferGroup.uniforms, + bufferInfo + ); this.device.queue.writeBuffer( bufferInfo.buffer, 0, @@ -1962,7 +2070,9 @@ function rendererWebGPU(p5, fn) { bufferInfo.data.byteLength ); - currentShader.buffersDirty.delete(bufferGroup.group * 1000 + bufferGroup.binding); + currentShader.buffersDirty.delete( + bufferGroup.group * 1000 + bufferGroup.binding + ); currentShader._cachedBindGroup[bufferGroup.group] = undefined; // Cache this buffer and data for next frame @@ -1999,39 +2109,52 @@ function rendererWebGPU(p5, fn) { bufferGroup?.currentBuffer || bufferGroup?.currentDynamicBuffer; if (uniformBufferInfo) { if (bufferGroup.dynamic) { - this.dynamicEntryOffsets[dynamicOffsetIdx++] = bufferGroup.lastOffset; + this.dynamicEntryOffsets[dynamicOffsetIdx++] = + bufferGroup.lastOffset; } if (!bindGroup) { bgEntries.push({ binding: entry.binding, resource: bufferGroup.dynamic ? { - buffer: uniformBufferInfo.uniformBuffer, - offset: 0, - size: Math.ceil(bufferGroup.size / this.uniformBufferAlignment) * this.uniformBufferAlignment, - } - : { buffer: uniformBufferInfo.buffer }, + buffer: uniformBufferInfo.uniformBuffer, + offset: 0, + size: + Math.ceil( + bufferGroup.size / this.uniformBufferAlignment + ) * this.uniformBufferAlignment + } + : { buffer: uniformBufferInfo.buffer } }); } } else if (entry.storage && !bindGroup) { // Storage buffer binding const uniform = currentShader.uniforms[entry.storage.name]; - if (!uniform || !uniform._cachedData || !uniform._cachedData._isStorageBuffer) { + if ( + !uniform || + !uniform._cachedData || + !uniform._cachedData._isStorageBuffer + ) { throw new Error( `Storage buffer "${entry.storage.name}" not set. ` + - `Use shader.setUniform("${entry.storage.name}", storageBuffer)` + `Use shader.setUniform("${entry.storage.name}", storageBuffer)` ); } bgEntries.push({ binding: entry.binding, - resource: { buffer: uniform._cachedData.buffer }, + resource: { buffer: uniform._cachedData.buffer } }); } else if (!bindGroup) { bgEntries.push({ binding: entry.binding, - resource: entry.uniform.type === 'sampler' - ? (entry.uniform.textureSource.texture || this._getEmptyTexture()).getSampler() - : (entry.uniform.texture || this._getEmptyTexture()).textureHandle.view, + resource: + entry.uniform.type === 'sampler' + ? ( + entry.uniform.textureSource.texture || + this._getEmptyTexture() + ).getSampler() + : (entry.uniform.texture || this._getEmptyTexture()) + .textureHandle.view }); } } @@ -2040,15 +2163,12 @@ function rendererWebGPU(p5, fn) { if (!bindGroup) { bindGroup = this.device.createBindGroup({ layout, - entries: bgEntries, + entries: bgEntries }); } currentShader._cachedBindGroup[group] = bindGroup; if (dynamicOffsetIdx === 0) { - passEncoder.setBindGroup( - group, - bindGroup, - ); + passEncoder.setBindGroup(group, bindGroup); } else { passEncoder.setBindGroup( group, @@ -2081,7 +2201,10 @@ function rendererWebGPU(p5, fn) { // Duck typing instead of instanceof to avoid importing a separate // copy of the Color/Vector classes if (value?.isVector) { - value = value.values.length !== value.dimensions ? value.values.slice(0, value.dimensions) : value.values; + value = + value.values.length !== value.dimensions + ? value.values.slice(0, value.dimensions) + : value.values; } else if (value?.isColor) { value = value._getRGBA([1, 1, 1, 1]); } @@ -2105,9 +2228,15 @@ function rendererWebGPU(p5, fn) { } else if (field.packInPlace) { // In-place packing for mat3: write directly to buffer with padding const base = byteOffset / 4; - floatView[base + 0] = value[0]; floatView[base + 1] = value[1]; floatView[base + 2] = value[2]; - floatView[base + 4] = value[3]; floatView[base + 5] = value[4]; floatView[base + 6] = value[5]; - floatView[base + 8] = value[6]; floatView[base + 9] = value[7]; floatView[base + 10] = value[8]; + floatView[base + 0] = value[0]; + floatView[base + 1] = value[1]; + floatView[base + 2] = value[2]; + floatView[base + 4] = value[3]; + floatView[base + 5] = value[4]; + floatView[base + 6] = value[5]; + floatView[base + 8] = value[6]; + floatView[base + 9] = value[7]; + floatView[base + 10] = value[8]; } else if (field.size === 4) { floatView.set([value], byteOffset / 4); } else { @@ -2123,14 +2252,22 @@ function rendererWebGPU(p5, fn) { for (const uniform of groupUniforms) { const fullUniform = shader.uniforms[uniform.name]; if (!fullUniform || fullUniform.isSampler) continue; - this._packField(fullUniform, fullUniform._mappedData, data, dataView, offset); + this._packField( + fullUniform, + fullUniform._mappedData, + data, + dataView, + offset + ); } } _hasGroupDataChanged(shader, bufferGroup) { // First time if (!bufferGroup.currentBuffer) return true; - return shader.buffersDirty.has(bufferGroup.group * 1000 + bufferGroup.binding); + return shader.buffersDirty.has( + bufferGroup.group * 1000 + bufferGroup.binding + ); } _parseStruct(shaderSource, structName) { @@ -2147,10 +2284,9 @@ function rendererWebGPU(p5, fn) { let index = 0; let offset = 0; - const elementRegex = - /(?:@location\((\d+)\)\s+)?(\w+):\s*([^\n]+?),?\n/g + const elementRegex = /(?:@location\((\d+)\)\s+)?(\w+):\s*([^\n]+?),?\n/g; - const baseAlignAndSize = (type) => { + const baseAlignAndSize = type => { if (['f32', 'i32', 'u32', 'bool'].includes(type)) { return { align: 4, size: 4, items: 1, baseType: type }; } @@ -2175,17 +2311,25 @@ function rendererWebGPU(p5, fn) { const dim = parseInt(type[3]); const align = dim === 2 ? 8 : 16; // Each column must be aligned - const size = Math.ceil(dim * 4 / align) * align * dim; + const size = Math.ceil((dim * 4) / align) * align * dim; // For mat3, use in-place packing to avoid array allocation - const pack = dim === 3 - ? (data) => [ - ...data.slice(0, 3), - ...data.slice(3, 6), - ...data.slice(6, 9), - ] - : undefined; + const pack = + dim === 3 + ? data => [ + ...data.slice(0, 3), + ...data.slice(3, 6), + ...data.slice(6, 9) + ] + : undefined; const packInPlace = dim === 3; - return { align, size, pack, packInPlace, items: dim * dim, baseType: 'f32' }; + return { + align, + size, + pack, + packInPlace, + items: dim * dim, + baseType: 'f32' + }; } if (/^array<.+>$/.test(type)) { const [, subtype, rawLength] = type.match(/^array<(.+),\s*(\d+)>/); @@ -2194,14 +2338,14 @@ function rendererWebGPU(p5, fn) { align: elemAlign, size: elemSize, items: elemItems, - pack: elemPack = (data) => [...data], + pack: elemPack = data => [...data], baseType: elemBaseType } = baseAlignAndSize(subtype); const stride = Math.ceil(elemSize / elemAlign) * elemAlign; - const pack = (data) => { + const pack = data => { const result = []; for (let i = 0; i < data.length; i += elemItems) { - const elemData = elemPack(data.slice(i, elemItems)) + const elemData = elemPack(data.slice(i, elemItems)); result.push(...elemData); for (let j = 0; j < stride / 4 - elemData.length; j++) { result.push(0); @@ -2222,7 +2366,8 @@ function rendererWebGPU(p5, fn) { while ((match = elementRegex.exec(structBody)) !== null) { const [_, location, name, type] = match; - const { size, align, pack, packInPlace, baseType } = baseAlignAndSize(type); + const { size, align, pack, packInPlace, baseType } = + baseAlignAndSize(type); offset = Math.ceil(offset / align) * align; const offsetEnd = offset + size; elements[name] = { @@ -2253,7 +2398,8 @@ function rendererWebGPU(p5, fn) { _getShaderAttributes(shader) { const mainMatch = /fn main\(.+:\s*([^\s\)]+)/.exec(shader._vertSrc); - if (!mainMatch) throw new Error("Can't find `fn main` in vertex shader source"); + if (!mainMatch) + throw new Error("Can't find `fn main` in vertex shader source"); const inputType = mainMatch[1]; return this._parseStruct(shader.vertSrc(), inputType); @@ -2267,10 +2413,14 @@ function rendererWebGPU(p5, fn) { // updated or cached all at once. const uniformGroups = []; - const uniformVarRegex = /@group\((\d+)\)\s+@binding\((\d+)\)\s+var\s+(\w+)\s*:\s*(\w+);/g; + const uniformVarRegex = + /@group\((\d+)\)\s+@binding\((\d+)\)\s+var\s+(\w+)\s*:\s*(\w+);/g; let match; - const src = shader.shaderType === 'compute' ? shader.computeSrc() : shader.vertSrc(); + const src = + shader.shaderType === 'compute' + ? shader.computeSrc() + : shader.vertSrc(); while ((match = uniformVarRegex.exec(src)) !== null) { const [_, groupNum, binding, varName, structType] = match; const bindingIndex = parseInt(binding); @@ -2286,7 +2436,9 @@ function rendererWebGPU(p5, fn) { } if (uniformGroups.length === 0 && shader.shaderType !== 'compute') { - throw new Error('Expected at least one uniform struct bound to @group(0)'); + throw new Error( + 'Expected at least one uniform struct bound to @group(0)' + ); } // While we're also keeping track of the groups, the API we expose @@ -2294,7 +2446,9 @@ function rendererWebGPU(p5, fn) { // individual struct items in the group.) const allUniforms = {}; for (const group of uniformGroups) { - for (const [uniformName, uniformData] of Object.entries(group.uniforms)) { + for (const [uniformName, uniformData] of Object.entries( + group.uniforms + )) { allUniforms[uniformName] = { ...uniformData, group: group.group, @@ -2310,11 +2464,13 @@ function rendererWebGPU(p5, fn) { // Extract samplers from group bindings const samplers = {}; // TODO: support other texture types - const samplerRegex = /@group\((\d+)\)\s*@binding\((\d+)\)\s*var\s+(\w+)\s*:\s*(texture_2d|sampler);/g; + const samplerRegex = + /@group\((\d+)\)\s*@binding\((\d+)\)\s*var\s+(\w+)\s*:\s*(texture_2d|sampler);/g; // Extract storage buffers const storageBuffers = {}; - const storageRegex = /@group\((\d+)\)\s*@binding\((\d+)\)\s*var\s+(\w+)\s*:\s*array<\w+>/g; + const storageRegex = + /@group\((\d+)\)\s*@binding\((\d+)\)\s*var\s+(\w+)\s*:\s*array<\w+>/g; // Track which bindings are taken by the struct properties we've parsed // (the rest should be textures/samplers) @@ -2346,16 +2502,16 @@ function rendererWebGPU(p5, fn) { name, type, isSampler: true, - noData: type === 'sampler', + noData: type === 'sampler' }; } for (const sampler of Object.values(samplers)) { if (sampler.type.startsWith('texture')) { const samplerName = sampler.name + '_sampler'; - const samplerNode = Object - .values(samplers) - .find((s) => s.name === samplerName); + const samplerNode = Object.values(samplers).find( + s => s.name === samplerName + ); if (!samplerNode) { throw new Error( `Every shader texture needs an accompanying sampler. Could not find sampler ${samplerName} for texture ${sampler.name}` @@ -2374,9 +2530,10 @@ function rendererWebGPU(p5, fn) { const key = `${groupIndex},${bindingIndex}`; const existing = storageBuffers[key]; // If any stage uses read_write, the bind group layout must use read_write - const finalAccessMode = (existing?.accessMode === 'read_write' || accessMode === 'read_write') - ? 'read_write' - : accessMode; + const finalAccessMode = + existing?.accessMode === 'read_write' || accessMode === 'read_write' + ? 'read_write' + : accessMode; storageBuffers[key] = { visibility: (existing?.visibility || 0) | visibility, @@ -2393,7 +2550,11 @@ function rendererWebGPU(p5, fn) { // Store storage buffers on shader for later use shader._storageBuffers = Object.values(storageBuffers); - return [...Object.values(allUniforms).sort((a, b) => a.index - b.index), ...Object.values(samplers), ...Object.values(storageBuffers)]; + return [ + ...Object.values(allUniforms).sort((a, b) => a.index - b.index), + ...Object.values(samplers), + ...Object.values(storageBuffers) + ]; } getNextBindingIndex({ vert, frag, compute }, group = 0) { @@ -2424,7 +2585,10 @@ function rendererWebGPU(p5, fn) { uniform.texture = data instanceof Texture ? data : this.getTexture(data); } else if (!data?._isStorageBuffer) { - uniform._mappedData = this._mapUniformData(uniform, uniform._cachedData); + uniform._mappedData = this._mapUniformData( + uniform, + uniform._cachedData + ); } shader.buffersDirty.add(uniform.group * 1000 + uniform.binding); } @@ -2441,11 +2605,11 @@ function rendererWebGPU(p5, fn) { const gpuTexture = this.device.createTexture({ size: [width, height], format, - usage: usage || ( + usage: + usage || GPUTextureUsage.TEXTURE_BINDING | - GPUTextureUsage.COPY_DST | - GPUTextureUsage.RENDER_ATTACHMENT - ), + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT }); return { gpuTexture, view: gpuTexture.createView() }; } @@ -2488,12 +2652,12 @@ function rendererWebGPU(p5, fn) { [constants.CLAMP]: 'clamp-to-edge', [constants.REPEAT]: 'repeat', [constants.MIRROR]: 'mirror-repeat' - } + }; const sampler = this.device.createSampler({ magFilter: constantMapping[texture.magFilter], minFilter: constantMapping[texture.minFilter], addressModeU: constantMapping[texture.wrapS], - addressModeV: constantMapping[texture.wrapT], + addressModeV: constantMapping[texture.wrapT] }); this.samplers.set(key, sampler); return sampler; @@ -2513,16 +2677,16 @@ function rendererWebGPU(p5, fn) { materialFragmentShader, { vertex: { - "void beforeVertex": "() {}", - "Vertex getObjectInputs": "(inputs: Vertex) { return inputs; }", - "Vertex getWorldInputs": "(inputs: Vertex) { return inputs; }", - "Vertex getCameraInputs": "(inputs: Vertex) { return inputs; }", - "void afterVertex": "() {}", + 'void beforeVertex': '() {}', + 'Vertex getObjectInputs': '(inputs: Vertex) { return inputs; }', + 'Vertex getWorldInputs': '(inputs: Vertex) { return inputs; }', + 'Vertex getCameraInputs': '(inputs: Vertex) { return inputs; }', + 'void afterVertex': '() {}' }, fragment: { - "void beforeFragment": "() {}", - "Inputs getPixelInputs": "(inputs: Inputs) { return inputs; }", - "vec4f combineColors": `(components: ColorComponents) { + 'void beforeFragment': '() {}', + 'Inputs getPixelInputs': '(inputs: Inputs) { return inputs; }', + 'vec4f combineColors': `(components: ColorComponents) { var rgb = vec3(0.0); rgb += components.diffuse * components.baseColor; rgb += components.ambient * components.ambientColor; @@ -2530,9 +2694,10 @@ function rendererWebGPU(p5, fn) { rgb += components.emissive; return vec4(rgb, components.opacity); }`, - "vec4f getFinalColor": "(color: vec4, texCoord: vec2) { return color; }", - "void afterFragment": "() {}", - }, + 'vec4f getFinalColor': + '(color: vec4, texCoord: vec2) { return color; }', + 'void afterFragment': '() {}' + } } ); } @@ -2547,17 +2712,18 @@ function rendererWebGPU(p5, fn) { colorFragmentShader, { vertex: { - "void beforeVertex": "() {}", - "Vertex getObjectInputs": "(inputs: Vertex) { return inputs; }", - "Vertex getWorldInputs": "(inputs: Vertex) { return inputs; }", - "Vertex getCameraInputs": "(inputs: Vertex) { return inputs; }", - "void afterVertex": "() {}", + 'void beforeVertex': '() {}', + 'Vertex getObjectInputs': '(inputs: Vertex) { return inputs; }', + 'Vertex getWorldInputs': '(inputs: Vertex) { return inputs; }', + 'Vertex getCameraInputs': '(inputs: Vertex) { return inputs; }', + 'void afterVertex': '() {}' }, fragment: { - "void beforeFragment": "() {}", - "vec4 getFinalColor": "(color: vec4, texCoord: vec2) { return color; }", - "void afterFragment": "() {}", - }, + 'void beforeFragment': '() {}', + 'vec4 getFinalColor': + '(color: vec4, texCoord: vec2) { return color; }', + 'void afterFragment': '() {}' + } } ); } @@ -2572,19 +2738,23 @@ function rendererWebGPU(p5, fn) { lineDefs + lineFragmentShader, { vertex: { - "void beforeVertex": "() {}", - "StrokeVertex getObjectInputs": "(inputs: StrokeVertex) { return inputs; }", - "StrokeVertex getWorldInputs": "(inputs: StrokeVertex) { return inputs; }", - "StrokeVertex getCameraInputs": "(inputs: StrokeVertex) { return inputs; }", - "void afterVertex": "() {}", + 'void beforeVertex': '() {}', + 'StrokeVertex getObjectInputs': + '(inputs: StrokeVertex) { return inputs; }', + 'StrokeVertex getWorldInputs': + '(inputs: StrokeVertex) { return inputs; }', + 'StrokeVertex getCameraInputs': + '(inputs: StrokeVertex) { return inputs; }', + 'void afterVertex': '() {}' }, fragment: { - "void beforeFragment": "() {}", - "Inputs getPixelInputs": "(inputs: Inputs) { return inputs; }", - "vec4 getFinalColor": "(color: vec4, texCoord: vec2) { return color; }", - "bool shouldDiscard": "(outside: bool) { return outside; };", - "void afterFragment": "() {}", - }, + 'void beforeFragment': '() {}', + 'Inputs getPixelInputs': '(inputs: Inputs) { return inputs; }', + 'vec4 getFinalColor': + '(color: vec4, texCoord: vec2) { return color; }', + 'bool shouldDiscard': '(outside: bool) { return outside; };', + 'void afterFragment': '() {}' + } } ); } @@ -2626,9 +2796,9 @@ function rendererWebGPU(p5, fn) { const activeFramebuffer = this.activeFramebuffer(); const depthTextureView = activeFramebuffer - ? (activeFramebuffer.aaDepthTexture - ? activeFramebuffer.aaDepthTextureView - : activeFramebuffer.depthTextureView) + ? activeFramebuffer.aaDepthTexture + ? activeFramebuffer.aaDepthTextureView + : activeFramebuffer.depthTextureView : this.depthTextureView; if (!depthTextureView) { @@ -2641,12 +2811,12 @@ function rendererWebGPU(p5, fn) { stencilStoreOp: 'store', stencilClearValue: 0, depthReadOnly: true, - stencilReadOnly: false, + stencilReadOnly: false }; const renderPassDescriptor = { colorAttachments: [], - depthStencilAttachment: depthStencilAttachment, + depthStencilAttachment: depthStencilAttachment }; const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); @@ -2668,9 +2838,9 @@ function rendererWebGPU(p5, fn) { const activeFramebuffer = this.activeFramebuffer(); const depthTextureView = activeFramebuffer - ? (activeFramebuffer.aaDepthTexture - ? activeFramebuffer.aaDepthTextureView - : activeFramebuffer.depthTextureView) + ? activeFramebuffer.aaDepthTexture + ? activeFramebuffer.aaDepthTextureView + : activeFramebuffer.depthTextureView : this.depthTextureView; if (!depthTextureView) { @@ -2683,12 +2853,12 @@ function rendererWebGPU(p5, fn) { stencilStoreOp: 'store', stencilClearValue: 1, depthReadOnly: true, - stencilReadOnly: false, + stencilReadOnly: false }; const renderPassDescriptor = { colorAttachments: [], - depthStencilAttachment: depthStencilAttachment, + depthStencilAttachment: depthStencilAttachment }; const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); @@ -2728,10 +2898,9 @@ function rendererWebGPU(p5, fn) { (_, condition, hookType, hookName, body) => { const target = condition === 'ifdef'; if ( - ( - !!shader.hooks.modified.vertex[`${hookType} ${hookName}`] || - !!shader.hooks.modified.fragment[`${hookType} ${hookName}`] - ) === target + (!!shader.hooks.modified.vertex[`${hookType} ${hookName}`] || + !!shader.hooks.modified.fragment[`${hookType} ${hookName}`]) === + target ) { return body; } else { @@ -2740,14 +2909,23 @@ function rendererWebGPU(p5, fn) { } ); - let [preMain, main, postMain] = src.split(/((?:@(?:vertex|fragment|compute)\s*(?:@workgroup_size\([^)]+\)\s*)?)?fn main[^{]+\{)/); + let [preMain, main, postMain] = src.split( + /((?:@(?:vertex|fragment|compute)\s*(?:@workgroup_size\([^)]+\)\s*)?)?fn main[^{]+\{)/ + ); const getBuiltinParamName = (mainSrc, builtinName) => { - const match = new RegExp(`@builtin\\s*\\(\\s*${builtinName}\\s*\\)\\s*(\\w+)\\s*:`).exec(mainSrc); + const match = new RegExp( + `@builtin\\s*\\(\\s*${builtinName}\\s*\\)\\s*(\\w+)\\s*:` + ).exec(mainSrc); return match ? match[1] : null; }; - const ensureBuiltinParam = (mainSrc, builtinName, fallbackName, typeName) => { + const ensureBuiltinParam = ( + mainSrc, + builtinName, + fallbackName, + typeName + ) => { const existingName = getBuiltinParamName(mainSrc, builtinName); if (existingName) { return { mainSrc, argName: existingName }; @@ -2762,16 +2940,22 @@ function rendererWebGPU(p5, fn) { return { mainSrc: injectedMain, argName: fallbackName }; }; - const getMainStructParameter = (mainSrc) => { + const getMainStructParameter = mainSrc => { const match = /fn main\s*\(\s*(\w+)\s*:\s*(\w+)/.exec(mainSrc); if (!match) return null; return { inputName: match[1], structName: match[2] }; }; const getStructBuiltinFieldName = (structName, builtinName) => { - const structMatch = new RegExp(`struct\\s+${structName}\\s*\\{([^}]*)\\}`, 's').exec(preMain); + const structMatch = new RegExp( + `struct\\s+${structName}\\s*\\{([^}]*)\\}`, + 's' + ).exec(preMain); if (!structMatch) return null; - const fieldMatch = new RegExp(`@builtin\\s*\\(\\s*${builtinName}\\s*\\)\\s*(\\w+)\\s*:`, 's').exec(structMatch[1]); + const fieldMatch = new RegExp( + `@builtin\\s*\\(\\s*${builtinName}\\s*\\)\\s*(\\w+)\\s*:`, + 's' + ).exec(structMatch[1]); return fieldMatch ? fieldMatch[1] : null; }; @@ -2785,10 +2969,20 @@ function rendererWebGPU(p5, fn) { let hookExtraArgs = []; if (shaderType === 'vertex') { - const ensuredInstance = ensureBuiltinParam(main, 'instance_index', 'instanceID', 'u32'); + const ensuredInstance = ensureBuiltinParam( + main, + 'instance_index', + 'instanceID', + 'u32' + ); main = ensuredInstance.mainSrc; - const ensuredVertex = ensureBuiltinParam(main, 'vertex_index', '_p5VertexId', 'u32'); + const ensuredVertex = ensureBuiltinParam( + main, + 'vertex_index', + '_p5VertexId', + 'u32' + ); main = ensuredVertex.mainSrc; hookExtraParams = ['instanceID: u32', '_p5VertexId: u32']; @@ -2800,7 +2994,10 @@ function rendererWebGPU(p5, fn) { if (!fragmentPositionArg) { const mainStructParam = getMainStructParameter(main); if (mainStructParam) { - const positionField = getStructBuiltinFieldName(mainStructParam.structName, 'position'); + const positionField = getStructBuiltinFieldName( + mainStructParam.structName, + 'position' + ); if (positionField) { fragmentPositionArg = `${mainStructParam.inputName}.${positionField}`; } @@ -2808,7 +3005,12 @@ function rendererWebGPU(p5, fn) { } if (!fragmentPositionArg) { - const ensuredPosition = ensureBuiltinParam(main, 'position', '_p5FragPos', 'vec4'); + const ensuredPosition = ensureBuiltinParam( + main, + 'position', + '_p5FragPos', + 'vec4' + ); main = ensuredPosition.mainSrc; fragmentPositionArg = ensuredPosition.argName; } @@ -2816,7 +3018,12 @@ function rendererWebGPU(p5, fn) { hookExtraParams = ['_p5FragPos: vec4']; hookExtraArgs = [fragmentPositionArg]; } else if (shaderType === 'compute') { - const ensuredGlobalId = ensureBuiltinParam(main, 'global_invocation_id', '_p5GlobalId', 'vec3'); + const ensuredGlobalId = ensureBuiltinParam( + main, + 'global_invocation_id', + '_p5GlobalId', + 'vec3' + ); main = ensuredGlobalId.mainSrc; hookExtraParams = ['_p5GlobalId: vec3']; @@ -2837,11 +3044,29 @@ function rendererWebGPU(p5, fn) { // Find the next available binding in group 0 // Use the source we're currently building (preMain) which has texture bindings. We can't call `fragSrc()` // or `vertSrc()` because we may be in one of those calls already, and might infinite loop - const nextBinding = this.getNextBindingIndex({ - vert: shaderType === 'vertex' ? preMain + (shader.hooks.vertex?.declarations ?? '') + shader.hooks.declarations : shader._vertSrc, - frag: shaderType === 'fragment' ? preMain + (shader.hooks.fragment?.declarations ?? '') + shader.hooks.declarations : shader._fragSrc, - compute: shaderType === 'compute' ? preMain + (shader.hooks.compute?.declarations ?? '') + shader.hooks.declarations : shader._computeSrc, - }, 0); + const nextBinding = this.getNextBindingIndex( + { + vert: + shaderType === 'vertex' + ? preMain + + (shader.hooks.vertex?.declarations ?? '') + + shader.hooks.declarations + : shader._vertSrc, + frag: + shaderType === 'fragment' + ? preMain + + (shader.hooks.fragment?.declarations ?? '') + + shader.hooks.declarations + : shader._fragSrc, + compute: + shaderType === 'compute' + ? preMain + + (shader.hooks.compute?.declarations ?? '') + + shader.hooks.declarations + : shader._computeSrc + }, + 0 + ); // Create HookUniforms struct and binding const hookUniformsDecl = ` @@ -2852,7 +3077,10 @@ ${hookUniformFields}} @group(0) @binding(${nextBinding}) var hooks: HookUniforms; `; // Insert before the first @group binding, or at the end if there are none - const replaced = preMain.replace(/(@group\(0\)\s+@binding)/, `${hookUniformsDecl}\n$1`); + const replaced = preMain.replace( + /(@group\(0\)\s+@binding)/, + `${hookUniformsDecl}\n$1` + ); if (replaced === preMain) { // No @group bindings found in base shader, append to preMain preMain = preMain + '\n' + hookUniformsDecl; @@ -2862,9 +3090,15 @@ ${hookUniformFields}} } // Handle varying variables by injecting them into VertexOutput and FragmentInput structs - if (shader.hooks.varyingVariables && shader.hooks.varyingVariables.length > 0) { + if ( + shader.hooks.varyingVariables && + shader.hooks.varyingVariables.length > 0 + ) { // Generate struct members for varying variables - let nextLocationIndex = this._getNextAvailableLocation(preMain, shaderType); + let nextLocationIndex = this._getNextAvailableLocation( + preMain, + shaderType + ); let varyingMembers = ''; for (const varyingVar of shader.hooks.varyingVariables) { @@ -2889,7 +3123,10 @@ ${hookUniformFields}} } // Add file-global varying variable declarations - if (shader.hooks.varyingVariables && shader.hooks.varyingVariables.length > 0) { + if ( + shader.hooks.varyingVariables && + shader.hooks.varyingVariables.length > 0 + ) { let varyingDeclarations = ''; for (const varyingVar of shader.hooks.varyingVariables) { // varyingVar is a string like "varName: vec3" @@ -2912,9 +3149,15 @@ ${hookUniformFields}} const returnMatch = postMain.match(/return\s+(\w+)\s*;/); if (returnMatch) { const outputVarName = returnMatch[1]; - copyStatements = copyStatements.replace(/OUTPUT_VAR/g, outputVarName); + copyStatements = copyStatements.replace( + /OUTPUT_VAR/g, + outputVarName + ); // Insert before the return statement - postMain = postMain.replace(/(return\s+\w+\s*;)/g, `${copyStatements} $1`); + postMain = postMain.replace( + /(return\s+\w+\s*;)/g, + `${copyStatements} $1` + ); } } else if (shaderType === 'fragment') { // In fragment shader, initialize varying variables from input struct at start of main @@ -2936,9 +3179,15 @@ ${hookUniformFields}} // Handle instanceID varying for fragment access if (shader.hooks.instanceIDVarying) { - const { name, declaration, source, interpolation } = shader.hooks.instanceIDVarying; - const nextLocIndex = this._getNextAvailableLocation(preMain, shaderType); - const interpAttr = interpolation ? ` @interpolate(${interpolation})` : ''; + const { name, declaration, source, interpolation } = + shader.hooks.instanceIDVarying; + const nextLocIndex = this._getNextAvailableLocation( + preMain, + shaderType + ); + const interpAttr = interpolation + ? ` @interpolate(${interpolation})` + : ''; const [varName, varType] = declaration.split(':').map(s => s.trim()); const structMember = `@location(${nextLocIndex})${interpAttr} ${declaration},`; @@ -2973,7 +3222,8 @@ ${hookUniformFields}} const mainStructParam = getMainStructParameter(main); if (mainStructParam) { const inputVarName = mainStructParam.inputName; - postMain = `\n ${varName} = ${inputVarName}.${varName};\n` + postMain; + postMain = + `\n ${varName} = ${inputVarName}.${varName};\n` + postMain; } } } @@ -2988,7 +3238,9 @@ ${hookUniformFields}} } for (const hookDef in shader.hooks.helpers) { const [hookType, hookName] = hookDef.split(' '); - const [_, params, body] = /^(\([^\)]*\))((?:.|\n)*)$/.exec(shader.hooks.helpers[hookDef]); + const [_, params, body] = /^(\([^\)]*\))((?:.|\n)*)$/.exec( + shader.hooks.helpers[hookDef] + ); if (hookType === 'void') { hooks += `fn ${hookName}${params}${body}\n`; } else { @@ -3005,7 +3257,9 @@ ${hookUniformFields}} shader.hooks.modified[shaderType][hookDef] ? 'true' : 'false' };\n`; - let [_, params, body] = /^(\([^\)]*\))((?:.|\n)*)$/.exec(shader.hooks[shaderType][hookDef]); + let [_, params, body] = /^(\([^\)]*\))((?:.|\n)*)$/.exec( + shader.hooks[shaderType][hookDef] + ); params = appendHookParams(params, hookExtraParams); @@ -3021,7 +3275,7 @@ ${hookUniformFields}} // extra args from right to left so position shifts don't // invalidate earlier insertion points. if (hookExtraArgs.length > 0) { - const addHookArgs = (src) => { + const addHookArgs = src => { const insertions = []; let searchIdx = 0; let m; @@ -3048,7 +3302,8 @@ ${hookUniformFields}} let result = src; for (const { pos, hasParams } of insertions) { - const insertion = (hasParams ? ', ' : '') + hookExtraArgs.join(', '); + const insertion = + (hasParams ? ', ' : '') + hookExtraArgs.join(', '); result = result.slice(0, pos) + insertion + result.slice(pos); } return result; @@ -3063,10 +3318,13 @@ ${hookUniformFields}} _getNextAvailableLocation(shaderSource, shaderType) { // Parse existing struct to find the highest @location number let maxLocation = -1; - const structName = shaderType === 'vertex' ? 'VertexOutput' : 'FragmentInput'; + const structName = + shaderType === 'vertex' ? 'VertexOutput' : 'FragmentInput'; // Find the struct definition - const structMatch = shaderSource.match(new RegExp(`struct\\s+${structName}\\s*\\{([^}]*)\\}`, 's')); + const structMatch = shaderSource.match( + new RegExp(`struct\\s+${structName}\\s*\\{([^}]*)\\}`, 's') + ); if (structMatch) { const structBody = structMatch[1]; @@ -3086,18 +3344,18 @@ ${hookUniformFields}} getShaderHookTypes(shader, hookName) { // Create mapping from WGSL types to DataType entries const wgslToDataType = { - 'f32': DataType.float1, + f32: DataType.float1, 'vec2': DataType.float2, 'vec3': DataType.float3, 'vec4': DataType.float4, - 'vec2f': DataType.float2, - 'vec3f': DataType.float3, - 'vec4f': DataType.float4, - 'i32': DataType.int1, + vec2f: DataType.float2, + vec3f: DataType.float3, + vec4f: DataType.float4, + i32: DataType.int1, 'vec2': DataType.int2, 'vec3': DataType.int3, 'vec4': DataType.int4, - 'bool': DataType.bool1, + bool: DataType.bool1, 'vec2': DataType.bool2, 'vec3': DataType.bool3, 'vec4': DataType.bool4, @@ -3126,12 +3384,16 @@ ${hookUniformFields}} const returnQualifiers = [...nameParts]; const parameterMatch = /\(([^\)]*)\)/.exec(body); if (!parameterMatch) { - throw new Error(`Couldn't find function parameters in hook body:\n${body}`); + throw new Error( + `Couldn't find function parameters in hook body:\n${body}` + ); } const structProperties = structName => { // WGSL struct parsing: struct StructName { field1: Type, field2: Type } - const structDefMatch = new RegExp(`struct\\s+${structName}\\s*{([^}]*)}`).exec(fullSrc); + const structDefMatch = new RegExp( + `struct\\s+${structName}\\s*{([^}]*)}` + ).exec(fullSrc); if (!structDefMatch) return undefined; const properties = []; @@ -3142,7 +3404,8 @@ ${hookUniformFields}} // Remove location decorations and parse field // Format: [@location(N)] fieldName: Type - const fieldMatch = /(?:@location\([^)]*\)\s*)?(\w+)\s*:\s*([^,\s]+)/.exec(trimmed); + const fieldMatch = + /(?:@location\([^)]*\)\s*)?(\w+)\s*:\s*([^,\s]+)/.exec(trimmed); if (!fieldMatch) continue; const name = fieldMatch[1]; @@ -3164,35 +3427,38 @@ ${hookUniformFields}} return properties; }; - const parameters = parameterMatch[1].split(',').map(paramString => { - // WGSL function parameters: name: type or name: binding - const trimmed = paramString.trim(); - if (!trimmed) return null; + const parameters = parameterMatch[1] + .split(',') + .map(paramString => { + // WGSL function parameters: name: type or name: binding + const trimmed = paramString.trim(); + if (!trimmed) return null; - const parts = trimmed.split(':').map(s => s.trim()); - if (parts.length !== 2) return null; + const parts = trimmed.split(':').map(s => s.trim()); + if (parts.length !== 2) return null; - const name = parts[0]; - let typeName = parts[1]; + const name = parts[0]; + let typeName = parts[1]; - // Handle texture bindings like "texture_2d" -> sampler2D DataType - if (typeName.includes('texture_2d')) { - typeName = 'texture_2d'; - } + // Handle texture bindings like "texture_2d" -> sampler2D DataType + if (typeName.includes('texture_2d')) { + typeName = 'texture_2d'; + } - const dataType = wgslToDataType[typeName] || null; + const dataType = wgslToDataType[typeName] || null; - const properties = structProperties(typeName); - return { - name, - type: { - typeName: typeName, // Keep native WGSL type name - qualifiers: [], - properties, - dataType: dataType - } - }; - }).filter(Boolean); + const properties = structProperties(typeName); + return { + name, + type: { + typeName: typeName, // Keep native WGSL type name + qualifiers: [], + properties, + dataType: dataType + } + }; + }) + .filter(Boolean); // Convert WGSL return type to DataType const returnDataType = wgslToDataType[returnType] || null; @@ -3227,7 +3493,7 @@ ${hookUniformFields}} const bufferSize = Math.max(requiredSize, this.pixelReadBufferSize * 2); this.pixelReadBuffer = this.device.createBuffer({ size: bufferSize, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }); this.pixelReadBufferSize = bufferSize; } @@ -3244,7 +3510,7 @@ ${hookUniformFields}} ////////////////////////////////////////////// defaultFramebufferAlpha() { - return true + return true; } defaultFramebufferAntialias() { @@ -3255,15 +3521,16 @@ ${hookUniformFields}} return true; } - createFramebufferResources(framebuffer) { - } + createFramebufferResources(framebuffer) {} validateFramebufferFormats(framebuffer) { - if (![ - constants.UNSIGNED_BYTE, - constants.FLOAT, - constants.HALF_FLOAT - ].includes(framebuffer.format)) { + if ( + ![ + constants.UNSIGNED_BYTE, + constants.FLOAT, + constants.HALF_FLOAT + ].includes(framebuffer.format) + ) { console.warn( 'Unknown Framebuffer format. ' + 'Please use UNSIGNED_BYTE, FLOAT, or HALF_FLOAT. ' + @@ -3272,10 +3539,12 @@ ${hookUniformFields}} framebuffer.format = constants.UNSIGNED_BYTE; } - if (framebuffer.useDepth && ![ - constants.UNSIGNED_INT, - constants.FLOAT - ].includes(framebuffer.depthFormat)) { + if ( + framebuffer.useDepth && + ![constants.UNSIGNED_INT, constants.FLOAT].includes( + framebuffer.depthFormat + ) + ) { console.warn( 'Unknown Framebuffer depth format. ' + 'Please use UNSIGNED_INT or FLOAT. Defaulting to FLOAT.' @@ -3312,21 +3581,24 @@ ${hookUniformFields}} size: { width: framebuffer.width * framebuffer.density, height: framebuffer.height * framebuffer.density, - depthOrArrayLayers: 1, + depthOrArrayLayers: 1 }, - format: this._getWebGPUColorFormat(framebuffer), + format: this._getWebGPUColorFormat(framebuffer) }; // Create non-multisampled texture for texture binding (always needed) const colorTextureDescriptor = { ...baseDescriptor, - usage: GPUTextureUsage.RENDER_ATTACHMENT | - GPUTextureUsage.TEXTURE_BINDING | - GPUTextureUsage.COPY_SRC | - (framebuffer._useCanvasFormat ? GPUTextureUsage.COPY_DST : 0), - sampleCount: 1, + usage: + GPUTextureUsage.RENDER_ATTACHMENT | + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_SRC | + (framebuffer._useCanvasFormat ? GPUTextureUsage.COPY_DST : 0), + sampleCount: 1 }; - framebuffer.colorTexture = this.device.createTexture(colorTextureDescriptor); + framebuffer.colorTexture = this.device.createTexture( + colorTextureDescriptor + ); framebuffer.colorTextureView = framebuffer.colorTexture.createView(); // Create multisampled texture for rendering if antialiasing is enabled @@ -3334,10 +3606,13 @@ ${hookUniformFields}} const aaColorTextureDescriptor = { ...baseDescriptor, usage: GPUTextureUsage.RENDER_ATTACHMENT, - sampleCount: this._getValidSampleCount(framebuffer.antialiasSamples), + sampleCount: this._getValidSampleCount(framebuffer.antialiasSamples) }; - framebuffer.aaColorTexture = this.device.createTexture(aaColorTextureDescriptor); - framebuffer.aaColorTextureView = framebuffer.aaColorTexture.createView(); + framebuffer.aaColorTexture = this.device.createTexture( + aaColorTextureDescriptor + ); + framebuffer.aaColorTextureView = + framebuffer.aaColorTexture.createView(); } if (framebuffer.useDepth) { @@ -3345,20 +3620,23 @@ ${hookUniformFields}} size: { width: framebuffer.width * framebuffer.density, height: framebuffer.height * framebuffer.density, - depthOrArrayLayers: 1, + depthOrArrayLayers: 1 }, - format: this._getWebGPUDepthFormat(framebuffer), + format: this._getWebGPUDepthFormat(framebuffer) }; // Create non-multisampled depth texture for texture binding (always needed) const depthTextureDescriptor = { ...depthBaseDescriptor, - usage: GPUTextureUsage.RENDER_ATTACHMENT | - GPUTextureUsage.TEXTURE_BINDING | - (framebuffer._useCanvasFormat ? GPUTextureUsage.COPY_DST : 0), - sampleCount: 1, + usage: + GPUTextureUsage.RENDER_ATTACHMENT | + GPUTextureUsage.TEXTURE_BINDING | + (framebuffer._useCanvasFormat ? GPUTextureUsage.COPY_DST : 0), + sampleCount: 1 }; - framebuffer.depthTexture = this.device.createTexture(depthTextureDescriptor); + framebuffer.depthTexture = this.device.createTexture( + depthTextureDescriptor + ); framebuffer.depthTextureView = framebuffer.depthTexture.createView(); // Create multisampled depth texture for rendering if antialiasing is enabled @@ -3366,10 +3644,13 @@ ${hookUniformFields}} const aaDepthTextureDescriptor = { ...depthBaseDescriptor, usage: GPUTextureUsage.RENDER_ATTACHMENT, - sampleCount: this._getValidSampleCount(framebuffer.antialiasSamples), + sampleCount: this._getValidSampleCount(framebuffer.antialiasSamples) }; - framebuffer.aaDepthTexture = this.device.createTexture(aaDepthTextureDescriptor); - framebuffer.aaDepthTextureView = framebuffer.aaDepthTexture.createView(); + framebuffer.aaDepthTexture = this.device.createTexture( + aaDepthTextureDescriptor + ); + framebuffer.aaDepthTextureView = + framebuffer.aaDepthTexture.createView(); } } @@ -3386,32 +3667,35 @@ ${hookUniformFields}} view: framebuffer.aaColorTexture ? framebuffer.aaColorTextureView : framebuffer.colorTextureView, - loadOp: "clear", - storeOp: "store", + loadOp: 'clear', + storeOp: 'store', clearValue: { r: 0, g: 0, b: 0, a: 0 }, resolveTarget: framebuffer.aaColorTexture ? framebuffer.colorTextureView - : undefined, + : undefined }; // Clear the depth texture if it exists - const depthTexture = framebuffer.aaDepthTexture || framebuffer.depthTexture; - const depthStencilAttachment = depthTexture ? { - view: framebuffer.aaDepthTexture - ? framebuffer.aaDepthTextureView - : framebuffer.depthTextureView, - depthLoadOp: "clear", - depthStoreOp: "store", - depthClearValue: 1.0, - stencilLoadOp: "clear", - stencilStoreOp: "store", - depthReadOnly: false, - stencilReadOnly: false, - } : undefined; + const depthTexture = + framebuffer.aaDepthTexture || framebuffer.depthTexture; + const depthStencilAttachment = depthTexture + ? { + view: framebuffer.aaDepthTexture + ? framebuffer.aaDepthTextureView + : framebuffer.depthTextureView, + depthLoadOp: 'clear', + depthStoreOp: 'store', + depthClearValue: 1.0, + stencilLoadOp: 'clear', + stencilStoreOp: 'store', + depthReadOnly: false, + stencilReadOnly: false + } + : undefined; const renderPassDescriptor = { colorAttachments: [colorAttachment], - depthStencilAttachment: depthStencilAttachment, + depthStencilAttachment: depthStencilAttachment }; const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); @@ -3450,7 +3734,9 @@ ${hookUniformFields}} if (value.dimensions === 2) return 'vec2f'; if (value.dimensions === 3) return 'vec3f'; if (value.dimensions === 4) return 'vec4f'; - throw new Error(`Unsupported vector dimension ${value.dimensions} for struct storage field`); + throw new Error( + `Unsupported vector dimension ${value.dimensions} for struct storage field` + ); } if (value?.isColor) { return 'vec4f'; @@ -3459,9 +3745,13 @@ ${hookUniformFields}} if (value.length === 2) return 'vec2f'; if (value.length === 3) return 'vec3f'; if (value.length === 4) return 'vec4f'; - throw new Error(`Unsupported array length ${value.length} for struct storage field`); + throw new Error( + `Unsupported array length ${value.length} for struct storage field` + ); } - throw new Error(`Unsupported value type ${typeof value} for struct storage field`); + throw new Error( + `Unsupported value type ${typeof value} for struct storage field` + ); } // Infers a struct schema from the first element of a struct array. @@ -3488,16 +3778,16 @@ ${hookUniformFields}} ) { p5._friendlyError( `The "${name}" property in your storage data contains a nested object. ` + - `Make sure you only use properties with numbers, arrays of numbers, or p5.Vector.`, + `Make sure you only use properties with numbers, arrays of numbers, or p5.Vector.`, 'createStorage' ); } } } - const fieldLines = entries.map(([name, value]) => - ` ${name}: ${this._jsValueToWgslType(value)},` - ).join('\n'); + const fieldLines = entries + .map(([name, value]) => ` ${name}: ${this._jsValueToWgslType(value)},`) + .join('\n'); const structBody = `{\n${fieldLines}\n}`; const elements = this._parseStruct(`struct _Tmp ${structBody}`, '_Tmp'); @@ -3510,9 +3800,11 @@ ${hookUniformFields}} const align = el.size <= 4 ? 4 : el.size <= 8 ? 8 : 16; maxAlign = Math.max(maxAlign, align); // Track original JS type for reconstruction during readback - const kind = value?.isVector ? 'vector' - : value?.isColor ? 'color' - : undefined; + const kind = value?.isVector + ? 'vector' + : value?.isColor + ? 'color' + : undefined; return { name, baseType: el.baseType, @@ -3520,7 +3812,7 @@ ${hookUniformFields}} offset: el.offset, packInPlace: el.packInPlace ?? false, dim: el.size / 4, - kind, + kind }; }); @@ -3541,7 +3833,13 @@ ${hookUniformFields}} const item = data[i]; const baseOffset = i * stride; for (const field of fields) { - this._packField(field, item[field.name], floatView, dataView, baseOffset); + this._packField( + field, + item[field.name], + floatView, + dataView, + baseOffset + ); } } return floatView; @@ -3591,8 +3889,10 @@ ${hookUniformFields}} // Scale back to the current colorMode range const maxes = this.states.colorMaxes[this.states.colorMode]; item[field.name] = this._pInst.color( - values[0] * maxes[0], values[1] * maxes[1], - values[2] * maxes[2], values[3] * maxes[3] + values[0] * maxes[0], + values[1] * maxes[1], + values[2] * maxes[2], + values[3] * maxes[3] ); } else { item[field.name] = values; @@ -3610,20 +3910,25 @@ ${hookUniformFields}} const device = this.device; // Struct array: an array of plain objects - if (Array.isArray(dataOrCount) && dataOrCount.length > 0 && - typeof dataOrCount[0] === 'object' && !Array.isArray(dataOrCount[0])) { + if ( + Array.isArray(dataOrCount) && + dataOrCount.length > 0 && + typeof dataOrCount[0] === 'object' && + !Array.isArray(dataOrCount[0]) + ) { if (!p5.disableFriendlyErrors && dataOrCount.length > 1) { const firstKeys = Object.keys(dataOrCount[0]); let warned = false; for (let i = 1; i < dataOrCount.length; i++) { const el = dataOrCount[i]; const elKeys = Object.keys(el); - const sameKeys = firstKeys.length === elKeys.length && + const sameKeys = + firstKeys.length === elKeys.length && firstKeys.every((k, j) => k === elKeys[j]); if (!sameKeys) { p5._friendlyError( `Element ${i} has different fields than element 0. ` + - `All elements should have the same properties.`, + `All elements should have the same properties.`, 'createStorage' ); break; @@ -3634,7 +3939,7 @@ ${hookUniformFields}} if (firstType !== elType) { p5._friendlyError( `The "${key}" property of element ${i} has type ${elType} ` + - `but element 0 has type ${firstType}. Proporties should have the same type across all elements.`, + `but element 0 has type ${firstType}. Proporties should have the same type across all elements.`, 'createStorage' ); warned = true; @@ -3649,8 +3954,11 @@ ${hookUniformFields}} const size = packed.byteLength; const buffer = device.createBuffer({ size, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, - mappedAtCreation: true, + usage: + GPUBufferUsage.STORAGE | + GPUBufferUsage.COPY_DST | + GPUBufferUsage.COPY_SRC, + mappedAtCreation: true }); new Float32Array(buffer.getMappedRange()).set(packed); buffer.unmap(); @@ -3672,7 +3980,9 @@ ${hookUniformFields}} } else if (Array.isArray(dataOrCount)) { initialData = new Float32Array(dataOrCount); } else { - throw new Error('createStorage expects a number or array/Float32Array'); + throw new Error( + 'createStorage expects a number or array/Float32Array' + ); } size = initialData.byteLength; } @@ -3683,7 +3993,10 @@ ${hookUniformFields}} // Create storage buffer with STORAGE | COPY_DST | COPY_SRC usage const buffer = device.createBuffer({ size, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + usage: + GPUBufferUsage.STORAGE | + GPUBufferUsage.COPY_DST | + GPUBufferUsage.COPY_SRC, mappedAtCreation: initialData.length > 0 }); @@ -3722,9 +4035,13 @@ ${hookUniformFields}} return this.depthFormat; } if (framebuffer.useStencil) { - return framebuffer.depthFormat === constants.FLOAT ? 'depth32float-stencil8' : 'depth24plus-stencil8'; + return framebuffer.depthFormat === constants.FLOAT + ? 'depth32float-stencil8' + : 'depth24plus-stencil8'; } else { - return framebuffer.depthFormat === constants.FLOAT ? 'depth32float' : 'depth24plus'; + return framebuffer.depthFormat === constants.FLOAT + ? 'depth32float' + : 'depth24plus'; } } @@ -3739,7 +4056,7 @@ ${hookUniformFields}} } deleteFramebufferTextures(framebuffer) { - this._deleteFramebufferTexture(framebuffer.color) + this._deleteFramebufferTexture(framebuffer.color); if (framebuffer.depth) this._deleteFramebufferTexture(framebuffer.depth); } @@ -3759,8 +4076,7 @@ ${hookUniformFields}} } } - getFramebufferToBind(framebuffer) { - } + getFramebufferToBind(framebuffer) {} updateFramebufferTexture(framebuffer, property) { // No-op for WebGPU since antialiasing is handled at pipeline level @@ -3788,7 +4104,7 @@ ${hookUniformFields}} // const stagingBuffer = this._ensurePixelReadBuffer(bufferSize); const stagingBuffer = this.device.createBuffer({ size: bufferSize, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }); const commandEncoder = this.device.createCommandEncoder(); @@ -3799,7 +4115,11 @@ ${hookUniformFields}} mipLevel: 0, aspect: 'all' }, - { buffer: stagingBuffer, bytesPerRow: alignedBytesPerRow, rowsPerImage: height }, + { + buffer: stagingBuffer, + bytesPerRow: alignedBytesPerRow, + rowsPerImage: height + }, { width, height, depthOrArrayLayers: 1 } ); @@ -3814,7 +4134,9 @@ ${hookUniformFields}} // If alignment was needed, extract the actual pixel data let result; if (alignedBytesPerRow === unalignedBytesPerRow) { - result = new Uint8Array(mappedRange.slice(0, width * height * bytesPerPixel)); + result = new Uint8Array( + mappedRange.slice(0, width * height * bytesPerPixel) + ); stagingBuffer.unmap(); } else { // Need to extract pixel data from aligned buffer @@ -3823,7 +4145,10 @@ ${hookUniformFields}} for (let y = 0; y < height; y++) { const srcOffset = y * alignedBytesPerRow; const dstOffset = y * unalignedBytesPerRow; - result.set(mappedData.subarray(srcOffset, srcOffset + unalignedBytesPerRow), dstOffset); + result.set( + mappedData.subarray(srcOffset, srcOffset + unalignedBytesPerRow), + dstOffset + ); } stagingBuffer.unmap(); } @@ -3862,7 +4187,6 @@ ${hookUniformFields}} const pixelData = new Uint8Array(mappedRange); const result = [pixelData[0], pixelData[1], pixelData[2], pixelData[3]]; - this._ensurePixelsAreRGBA(framebuffer, result); stagingBuffer.unmap(); @@ -3874,8 +4198,8 @@ ${hookUniformFields}} // await this.finishDraw(); // const wasActive = this.activeFramebuffer() === framebuffer; // if (wasActive) { - // framebuffer.end(); - // this.flushDraw() + // framebuffer.end(); + // this.flushDraw() // } // Ensure all pending GPU work is complete before reading pixels // await this.queue.onSubmittedWorkDone(); @@ -3894,7 +4218,11 @@ ${hookUniformFields}} { texture: framebuffer.colorTexture, mipLevel: 0, - origin: { x: x * framebuffer.density, y: y * framebuffer.density, z: 0 } + origin: { + x: x * framebuffer.density, + y: y * framebuffer.density, + z: 0 + } }, { buffer: stagingBuffer, bytesPerRow: alignedBytesPerRow }, { width, height, depthOrArrayLayers: 1 } @@ -3907,7 +4235,9 @@ ${hookUniformFields}} let pixelData; if (alignedBytesPerRow === unalignedBytesPerRow) { - pixelData = new Uint8Array(mappedRange.slice(0, width * height * bytesPerPixel)); + pixelData = new Uint8Array( + mappedRange.slice(0, width * height * bytesPerPixel) + ); } else { // Need to extract pixel data from aligned buffer pixelData = new Uint8Array(width * height * bytesPerPixel); @@ -3915,14 +4245,19 @@ ${hookUniformFields}} for (let y = 0; y < height; y++) { const srcOffset = y * alignedBytesPerRow; const dstOffset = y * unalignedBytesPerRow; - pixelData.set(mappedData.subarray(srcOffset, srcOffset + unalignedBytesPerRow), dstOffset); + pixelData.set( + mappedData.subarray(srcOffset, srcOffset + unalignedBytesPerRow), + dstOffset + ); } } this._ensurePixelsAreRGBA(framebuffer, pixelData); // WebGPU doesn't need vertical flipping unlike WebGL const region = new Image(width, height); - region.imageData = region.canvas.getContext('2d').createImageData(width, height); + region.imageData = region.canvas + .getContext('2d') + .createImageData(width, height); region.imageData.data.set(pixelData); region.pixels = region.imageData.data; region.updatePixels(); @@ -3965,7 +4300,10 @@ ${hookUniformFields}} _ensurePixelsAreRGBA(framebuffer, result) { // Convert BGRA to RGBA if reading from canvas-format framebuffer on BGRA systems - if (framebuffer._useCanvasFormat && this.presentationFormat === 'bgra8unorm') { + if ( + framebuffer._useCanvasFormat && + this.presentationFormat === 'bgra8unorm' + ) { this._convertBGRtoRGB(result); } } @@ -3998,7 +4336,6 @@ ${hookUniformFields}} return super.filter(...args); } - baseFilterShader() { if (!this._baseFilterShader) { this._baseFilterShader = new Shader( @@ -4008,13 +4345,13 @@ ${hookUniformFields}} { vertex: {}, fragment: { - "vec4 getColor": `(inputs: FilterInputs, canvasContent: texture_2d, canvasContent_sampler: sampler) -> vec4 { + 'vec4 getColor': `(inputs: FilterInputs, canvasContent: texture_2d, canvasContent_sampler: sampler) -> vec4 { return textureSample(tex, tex_sampler, inputs.texCoord); - }`, + }` }, hookAliases: { - 'getColor': ['filterColor'], - }, + getColor: ['filterColor'] + } } ); } @@ -4023,15 +4360,11 @@ ${hookUniformFields}} baseComputeShader() { if (!this._baseComputeShader) { - this._baseComputeShader = new Shader( - this, - baseComputeShader, - { - compute: { - 'void iteration': '(index: vec3) {}', - }, + this._baseComputeShader = new Shader(this, baseComputeShader, { + compute: { + 'void iteration': '(index: vec3) {}' } - ); + }); } return this._baseComputeShader; } @@ -4070,11 +4403,14 @@ ${hookUniformFields}} size: { width: size, height: size, - depthOrArrayLayers: 1, + depthOrArrayLayers: 1 }, mipLevelCount: mipLevels, format: 'rgba8unorm', - usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT }; const gpuTexture = this.device.createTexture(textureDescriptor); @@ -4101,17 +4437,17 @@ ${hookUniformFields}} commandEncoder.copyTextureToTexture( { texture: sourceTexture, - origin: { x: 0, y: 0, z: 0 }, + origin: { x: 0, y: 0, z: 0 } }, { texture: mipmapData.gpuTexture, mipLevel: mipLevel, - origin: { x: 0, y: 0, z: 0 }, + origin: { x: 0, y: 0, z: 0 } }, { width: width, height: height, - depthOrArrayLayers: 1, + depthOrArrayLayers: 1 } ); @@ -4167,7 +4503,10 @@ ${hookUniformFields}} let pz = z; // we spread if we exceed GPU limits OR if it involves a large 1D dispatch - const exceedsLimits = x > MAX_THREADS_PER_DIM || y > MAX_THREADS_PER_DIM || z > MAX_THREADS_PER_DIM; + const exceedsLimits = + x > MAX_THREADS_PER_DIM || + y > MAX_THREADS_PER_DIM || + z > MAX_THREADS_PER_DIM; const isLarge1D = totalIterations > 1024 && y === 1 && z === 1; if (exceedsLimits || isLarge1D) { @@ -4189,11 +4528,15 @@ ${hookUniformFields}} const passEncoder = commandEncoder.beginComputePass(); this.setupShaderBindGroups(shader, passEncoder, { compute: true, - workgroupSize: [WORKGROUP_SIZE_X, WORKGROUP_SIZE_Y, WORKGROUP_SIZE_Z], + workgroupSize: [WORKGROUP_SIZE_X, WORKGROUP_SIZE_Y, WORKGROUP_SIZE_Z] }); // Dispatch compute workgroups - passEncoder.dispatchWorkgroups(workgroupCountX, workgroupCountY, workgroupCountZ); + passEncoder.dispatchWorkgroups( + workgroupCountX, + workgroupCountY, + workgroupCountZ + ); passEncoder.end(); this.device.queue.submit([commandEncoder.finish()]); @@ -4207,12 +4550,11 @@ ${hookUniformFields}} // TODO: move this and the duplicate in the WebGL renderer to another file fn.setAttributes = async function (key, value) { return this._renderer._setAttributes(key, value); - } - + }; } export default rendererWebGPU; -if (typeof p5 !== "undefined") { +if (typeof p5 !== 'undefined') { rendererWebGPU(p5, p5.prototype); } diff --git a/src/webgpu/shaders/filters/base.js b/src/webgpu/shaders/filters/base.js index d89704afed..4e8a99c34b 100644 --- a/src/webgpu/shaders/filters/base.js +++ b/src/webgpu/shaders/filters/base.js @@ -22,7 +22,9 @@ struct CameraUniforms { @group(2) @binding(0) var camera: CameraUniforms; `; -export const baseFilterVertexShader = filterUniforms + ` +export const baseFilterVertexShader = + filterUniforms + + ` struct VertexInput { @location(0) aPosition: vec3, @location(1) aTexCoord: vec2, @@ -50,7 +52,9 @@ fn main(input: VertexInput) -> VertexOutput { } `; -export const baseFilterFragmentShader = filterUniforms + ` +export const baseFilterFragmentShader = + filterUniforms + + ` struct FilterInputs { texCoord: vec2, canvasSize: vec2, diff --git a/src/webgpu/shaders/functions/noise3DWGSL.js b/src/webgpu/shaders/functions/noise3DWGSL.js index 1fb96f5fc6..4749cc1256 100644 --- a/src/webgpu/shaders/functions/noise3DWGSL.js +++ b/src/webgpu/shaders/functions/noise3DWGSL.js @@ -103,4 +103,4 @@ fn noise(st: vec3, octaves: i32, ampFalloff: f32) -> f32 { amplitude *= ampFalloff; } return (result + 1.0) * 0.5; -}`; \ No newline at end of file +}`; diff --git a/src/webgpu/shaders/line.js b/src/webgpu/shaders/line.js index 2b58857286..7d8883708d 100644 --- a/src/webgpu/shaders/line.js +++ b/src/webgpu/shaders/line.js @@ -368,4 +368,3 @@ fn main(input: StrokeFragmentInput) -> @location(0) vec4 { return vec4(col); } `; - diff --git a/src/webgpu/strands_wgslBackend.js b/src/webgpu/strands_wgslBackend.js index 0ba1c29772..2d193f172f 100644 --- a/src/webgpu/strands_wgslBackend.js +++ b/src/webgpu/strands_wgslBackend.js @@ -2,8 +2,20 @@ import noiseWGSL from './shaders/functions/noise3DWGSL.js'; import randomWGSL from './shaders/functions/randomWGSL'; import randomVertWGSL from './shaders/functions/randomVertWGSL'; import randomComputeWGSL from './shaders/functions/randomComputeWGSL'; -import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType, INSTANCE_ID_VARYING_NAME, HOOK_PARAM_PREFIX } from "../strands/ir_types"; -import { getNodeDataFromID, extractNodeTypeInfo } from "../strands/ir_dag"; +import { + NodeType, + OpCodeToSymbol, + BlockType, + OpCode, + NodeTypeToName, + isStructType, + BaseType, + StatementType, + DataType, + INSTANCE_ID_VARYING_NAME, + HOOK_PARAM_PREFIX +} from '../strands/ir_types'; +import { getNodeDataFromID, extractNodeTypeInfo } from '../strands/ir_dag'; import * as FES from '../strands/strands_FES'; import * as build from '../strands/ir_builders'; import { createStrandsNode } from '../strands/strands_node'; @@ -15,22 +27,22 @@ function shouldCreateTemp(dag, nodeID) { return uses.length > 1; } const TypeNames = { - 'float1': 'f32', - 'float2': 'vec2', - 'float3': 'vec3', - 'float4': 'vec4', - 'int1': 'i32', - 'int2': 'vec2', - 'int3': 'vec3', - 'int4': 'vec4', - 'bool1': 'bool', - 'bool2': 'vec2', - 'bool3': 'vec3', - 'bool4': 'vec4', - 'mat2': 'mat2x2', - 'mat3': 'mat3x3', - 'mat4': 'mat4x4', -} + float1: 'f32', + float2: 'vec2', + float3: 'vec3', + float4: 'vec4', + int1: 'i32', + int2: 'vec2', + int3: 'vec3', + int4: 'vec4', + bool1: 'bool', + bool2: 'vec2', + bool3: 'vec3', + bool4: 'vec4', + mat2: 'mat2x2', + mat3: 'mat3x3', + mat4: 'mat4x4' +}; const cfgHandlers = { [BlockType.DEFAULT]: (blockID, strandsContext, generationContext) => { const { dag, cfg } = strandsContext; @@ -38,7 +50,11 @@ const cfgHandlers = { for (const nodeID of instructions) { const nodeType = dag.nodeTypes[nodeID]; if (shouldCreateTemp(dag, nodeID)) { - const declaration = wgslBackend.generateDeclaration(generationContext, dag, nodeID); + const declaration = wgslBackend.generateDeclaration( + generationContext, + dag, + nodeID + ); generationContext.write(declaration); } if (nodeType === NodeType.STATEMENT) { @@ -101,7 +117,11 @@ const cfgHandlers = { [BlockType.IF_COND](blockID, strandsContext, generationContext) { const { dag, cfg } = strandsContext; const conditionID = cfg.blockConditions[blockID]; - const condExpr = wgslBackend.generateExpression(generationContext, dag, conditionID); + const condExpr = wgslBackend.generateExpression( + generationContext, + dag, + conditionID + ); generationContext.write(`if (${condExpr})`); this[BlockType.DEFAULT](blockID, strandsContext, generationContext); }, @@ -145,7 +165,11 @@ const cfgHandlers = { generationContext.suppressSemicolon = isLast; if (shouldCreateTemp(dag, nodeID)) { - const declaration = wgslBackend.generateDeclaration(generationContext, dag, nodeID); + const declaration = wgslBackend.generateDeclaration( + generationContext, + dag, + nodeID + ); generationContext.write(declaration); } if (node.nodeType === NodeType.STATEMENT) { @@ -177,29 +201,40 @@ const cfgHandlers = { const sourceNodeID = node.dependsOn[branchIndex]; const tempName = generationContext.tempNames[nodeID]; if (tempName && sourceNodeID !== null) { - const sourceExpr = wgslBackend.generateExpression(generationContext, dag, sourceNodeID); + const sourceExpr = wgslBackend.generateExpression( + generationContext, + dag, + sourceNodeID + ); generationContext.write(`${tempName} = ${sourceExpr};`); } } } } } - }, -} + } +}; export const wgslBackend = { hookEntry(hookType) { - const params = hookType.parameters.map((param) => { - // For struct types, use a raw prefix since we'll create a mutable copy - const paramName = param.type.properties ? `_p5_strands_raw_${param.name}` : `${HOOK_PARAM_PREFIX}${param.name}`; - return `${paramName}: ${param.type.typeName}`; - }).join(', '); + const params = hookType.parameters + .map(param => { + // For struct types, use a raw prefix since we'll create a mutable copy + const paramName = param.type.properties + ? `_p5_strands_raw_${param.name}` + : `${HOOK_PARAM_PREFIX}${param.name}`; + return `${paramName}: ${param.type.typeName}`; + }) + .join(', '); const firstLine = `(${params}) {`; // Generate mutable copies for struct parameters const mutableCopies = hookType.parameters .filter(param => param.type.properties) // Only struct types - .map(param => ` var ${HOOK_PARAM_PREFIX}${param.name} = _p5_strands_raw_${param.name};`) + .map( + param => + ` var ${HOOK_PARAM_PREFIX}${param.name} = _p5_strands_raw_${param.name};` + ) .join('\n'); return mutableCopies ? firstLine + '\n' + mutableCopies : firstLine; @@ -211,10 +246,10 @@ export const wgslBackend = { let bindingIndex = strandsContext.renderer.getNextBindingIndex({ vert: strandsContext.baseShader._vertSrc, frag: strandsContext.baseShader._fragSrc, - compute: strandsContext.baseShader._computeSrc, + compute: strandsContext.baseShader._computeSrc }); - for (const {name, typeInfo} of strandsContext.uniforms) { + for (const { name, typeInfo } of strandsContext.uniforms) { if (typeInfo.baseType === 'sampler2D') { const textureBinding = `@group(0) @binding(${bindingIndex}) var ${name}: texture_2d;`; const samplerBinding = `@group(0) @binding(${bindingIndex + 1}) var ${name}_sampler: sampler;`; @@ -235,10 +270,10 @@ export const wgslBackend = { let bindingIndex = strandsContext.renderer.getNextBindingIndex({ vert: strandsContext.baseShader._vertSrc, frag: strandsContext.baseShader._fragSrc, - compute: strandsContext.baseShader._computeSrc, + compute: strandsContext.baseShader._computeSrc }); - for (const {name, typeInfo} of strandsContext.uniforms) { + for (const { name, typeInfo } of strandsContext.uniforms) { if (typeInfo.baseType === 'storage') { const accessMode = isComputeShader ? 'read_write' : 'read'; let declaration; @@ -261,7 +296,7 @@ export const wgslBackend = { } }, getTypeName(baseType, dimension) { - const primitiveTypeName = TypeNames[baseType + dimension] + const primitiveTypeName = TypeNames[baseType + dimension]; if (!primitiveTypeName) { return baseType; } @@ -320,7 +355,11 @@ export const wgslBackend = { } else if (node.statementType === StatementType.EARLY_RETURN) { if (node.dependsOn && node.dependsOn.length > 0) { const exprNodeID = node.dependsOn[0]; - const expr = this.generateExpression(generationContext, dag, exprNodeID); + const expr = this.generateExpression( + generationContext, + dag, + exprNodeID + ); generationContext.write(`return ${expr}${semicolon}`); } else { generationContext.write(`return${semicolon}`); @@ -339,11 +378,27 @@ export const wgslBackend = { // Check if target is an array access (storage buffer assignment) if (targetNode.opCode === OpCode.Binary.ARRAY_ACCESS) { const [bufferID, indexID] = targetNode.dependsOn; - const bufferExpr = this.generateExpression(generationContext, dag, bufferID); - const indexExpr = this.generateExpression(generationContext, dag, indexID); - const sourceExpr = this.generateExpression(generationContext, dag, sourceNodeID); - const fieldSuffix = targetNode.identifier ? `.${targetNode.identifier}` : ''; - generationContext.write(`${bufferExpr}[i32(${indexExpr})]${fieldSuffix} = ${sourceExpr}${semicolon}`); + const bufferExpr = this.generateExpression( + generationContext, + dag, + bufferID + ); + const indexExpr = this.generateExpression( + generationContext, + dag, + indexID + ); + const sourceExpr = this.generateExpression( + generationContext, + dag, + sourceNodeID + ); + const fieldSuffix = targetNode.identifier + ? `.${targetNode.identifier}` + : ''; + generationContext.write( + `${bufferExpr}[i32(${indexExpr})]${fieldSuffix} = ${sourceExpr}${semicolon}` + ); return; } @@ -351,10 +406,18 @@ export const wgslBackend = { if (targetNode.opCode === OpCode.Unary.SWIZZLE) { const parentID = targetNode.dependsOn[0]; const parentNode = getNodeDataFromID(dag, parentID); - const parentExpr = this.generateExpression(generationContext, dag, parentID); + const parentExpr = this.generateExpression( + generationContext, + dag, + parentID + ); const swizzle = targetNode.swizzle; const parentDimension = parentNode.dimension; - const sourceExpr = this.generateExpression(generationContext, dag, sourceNodeID); + const sourceExpr = this.generateExpression( + generationContext, + dag, + sourceNodeID + ); // Create an array for each element of the target variable const componentMap = []; @@ -363,7 +426,7 @@ export const wgslBackend = { } // Map swizzle characters to component indices - const getComponentIndex = (char) => { + const getComponentIndex = char => { if ('xyzw'.includes(char)) return 'xyzw'.indexOf(char); if ('rgba'.includes(char)) return 'rgba'.indexOf(char); return -1; @@ -372,22 +435,38 @@ export const wgslBackend = { // Update the component map based on the swizzle assignment for (let i = 0; i < swizzle.length; i++) { const targetComponentIndex = getComponentIndex(swizzle[i]); - if (targetComponentIndex >= 0 && targetComponentIndex < parentDimension) { + if ( + targetComponentIndex >= 0 && + targetComponentIndex < parentDimension + ) { componentMap[targetComponentIndex] = { target: 'rhs', index: i }; } } // Generate the reconstruction expression - const vectorTypeName = this.getTypeName(parentNode.baseType, parentDimension); + const vectorTypeName = this.getTypeName( + parentNode.baseType, + parentDimension + ); const components = componentMap.map(({ target, index }) => { - return `${target === 'self' ? parentExpr : sourceExpr}.${'xyzw'[index]}` + return `${target === 'self' ? parentExpr : sourceExpr}.${'xyzw'[index]}`; }); - generationContext.write(`${parentExpr} = ${vectorTypeName}(${components.join(', ')})${semicolon}`); + generationContext.write( + `${parentExpr} = ${vectorTypeName}(${components.join(', ')})${semicolon}` + ); } else { // Regular assignment - const targetExpr = this.generateExpression(generationContext, dag, targetNodeID); - const sourceExpr = this.generateExpression(generationContext, dag, sourceNodeID); + const targetExpr = this.generateExpression( + generationContext, + dag, + targetNodeID + ); + const sourceExpr = this.generateExpression( + generationContext, + dag, + sourceNodeID + ); // Generate assignment if we have both target and source if (targetExpr && sourceExpr && targetExpr !== sourceExpr) { @@ -403,7 +482,12 @@ export const wgslBackend = { const typeName = this.getTypeName(T.baseType, T.dimension); return `var ${tmp}: ${typeName} = ${expr};`; }, - generateReturnStatement(strandsContext, generationContext, rootNodeID, returnType) { + generateReturnStatement( + strandsContext, + generationContext, + rootNodeID, + returnType + ) { if (!returnType) { generationContext.write('return;'); return; @@ -414,15 +498,21 @@ export const wgslBackend = { const structTypeInfo = returnType; for (let i = 0; i < structTypeInfo.properties.length; i++) { const prop = structTypeInfo.properties[i]; - const val = this.generateExpression(generationContext, dag, rootNode.dependsOn[i]); + const val = this.generateExpression( + generationContext, + dag, + rootNode.dependsOn[i] + ); if (prop.name !== val) { generationContext.write( `${rootNode.identifier}.${prop.name} = ${val};` - ) + ); } } } - generationContext.write(`return ${this.generateExpression(generationContext, dag, rootNodeID)};`); + generationContext.write( + `return ${this.generateExpression(generationContext, dag, rootNodeID)};` + ); }, generateExpression(generationContext, dag, nodeID) { const node = getNodeDataFromID(dag, nodeID); @@ -431,160 +521,221 @@ export const wgslBackend = { } switch (node.nodeType) { case NodeType.LITERAL: - if (node.baseType === BaseType.FLOAT) { - return node.value.toFixed(4); - } - else { - return node.value; - } + if (node.baseType === BaseType.FLOAT) { + return node.value.toFixed(4); + } else { + return node.value; + } case NodeType.VARIABLE: - // Track shared variable usage context - if (generationContext.shaderContext && generationContext.strandsContext?.sharedVariables?.has(node.identifier)) { - const sharedVar = generationContext.strandsContext.sharedVariables.get(node.identifier); - if (generationContext.shaderContext === 'vertex') { - sharedVar.usedInVertex = true; - } else if (generationContext.shaderContext === 'fragment') { - sharedVar.usedInFragment = true; + // Track shared variable usage context + if ( + generationContext.shaderContext && + generationContext.strandsContext?.sharedVariables?.has( + node.identifier + ) + ) { + const sharedVar = + generationContext.strandsContext.sharedVariables.get( + node.identifier + ); + if (generationContext.shaderContext === 'vertex') { + sharedVar.usedInVertex = true; + } else if (generationContext.shaderContext === 'fragment') { + sharedVar.usedInFragment = true; + } } - } - // Detect instanceID usage in fragment context and rewrite to varying name - if (node.identifier === this.instanceIdReference() && generationContext.shaderContext === 'fragment') { - generationContext.strandsContext._instanceIDUsedInFragment = true; - return INSTANCE_ID_VARYING_NAME; - } + // Detect instanceID usage in fragment context and rewrite to varying name + if ( + node.identifier === this.instanceIdReference() && + generationContext.shaderContext === 'fragment' + ) { + generationContext.strandsContext._instanceIDUsedInFragment = true; + return INSTANCE_ID_VARYING_NAME; + } - // Check if this is a uniform variable (but not a texture or storage buffer) - const uniform = generationContext.strandsContext?.uniforms?.find(uniform => uniform.name === node.identifier); - if (uniform && uniform.typeInfo.baseType !== 'sampler2D' && uniform.typeInfo.baseType !== 'storage') { - return `hooks.${node.identifier}`; - } + // Check if this is a uniform variable (but not a texture or storage buffer) + const uniform = generationContext.strandsContext?.uniforms?.find( + uniform => uniform.name === node.identifier + ); + if ( + uniform && + uniform.typeInfo.baseType !== 'sampler2D' && + uniform.typeInfo.baseType !== 'storage' + ) { + return `hooks.${node.identifier}`; + } - return node.identifier; + return node.identifier; case NodeType.OPERATION: - const useParantheses = node.usedBy.length > 0; - if (node.opCode === OpCode.Nary.CONSTRUCTOR) { - // TODO: differentiate casts and constructors for more efficient codegen. - // if (node.dependsOn.length === 1 && node.dimension === 1) { - // return this.generateExpression(generationContext, dag, node.dependsOn[0]); - // } - if (node.baseType === BaseType.SAMPLER2D) { - return this.generateExpression(generationContext, dag, node.dependsOn[0]); - } - const T = this.getTypeName(node.baseType, node.dimension); - const deps = node.dependsOn.map((dep) => this.generateExpression(generationContext, dag, dep)); - return `${T}(${deps.join(', ')})`; - } - if (node.opCode === OpCode.Nary.TERNARY) { - const [condID, trueID, falseID] = node.dependsOn; - const cond = this.generateExpression(generationContext, dag, condID); - const trueExpr = this.generateExpression(generationContext, dag, trueID); - const falseExpr = this.generateExpression(generationContext, dag, falseID); - return `select(${falseExpr}, ${trueExpr}, ${cond})`; - } - if (node.opCode === OpCode.Nary.FUNCTION_CALL) { - // Convert mod() function calls to % operator in WGSL - if (node.identifier === 'mod' && node.dependsOn.length === 2) { - const [leftID, rightID] = node.dependsOn; - const left = this.generateExpression(generationContext, dag, leftID); - const right = this.generateExpression(generationContext, dag, rightID); - const useParantheses = node.usedBy.length > 0; - if (useParantheses) { - return `(${left} % ${right})`; - } else { - return `${left} % ${right}`; + const useParantheses = node.usedBy.length > 0; + if (node.opCode === OpCode.Nary.CONSTRUCTOR) { + // TODO: differentiate casts and constructors for more efficient codegen. + // if (node.dependsOn.length === 1 && node.dimension === 1) { + // return this.generateExpression(generationContext, dag, node.dependsOn[0]); + // } + if (node.baseType === BaseType.SAMPLER2D) { + return this.generateExpression( + generationContext, + dag, + node.dependsOn[0] + ); } + const T = this.getTypeName(node.baseType, node.dimension); + const deps = node.dependsOn.map(dep => + this.generateExpression(generationContext, dag, dep) + ); + return `${T}(${deps.join(', ')})`; } - - // Convert atan(y, x) to atan2(y, x) in WGSL - if (node.identifier === 'atan' && node.dependsOn.length === 2) { - const functionArgs = node.dependsOn.map(arg => this.generateExpression(generationContext, dag, arg)); - return `atan2(${functionArgs.join(', ')})`; + if (node.opCode === OpCode.Nary.TERNARY) { + const [condID, trueID, falseID] = node.dependsOn; + const cond = this.generateExpression(generationContext, dag, condID); + const trueExpr = this.generateExpression( + generationContext, + dag, + trueID + ); + const falseExpr = this.generateExpression( + generationContext, + dag, + falseID + ); + return `select(${falseExpr}, ${trueExpr}, ${cond})`; } + if (node.opCode === OpCode.Nary.FUNCTION_CALL) { + // Convert mod() function calls to % operator in WGSL + if (node.identifier === 'mod' && node.dependsOn.length === 2) { + const [leftID, rightID] = node.dependsOn; + const left = this.generateExpression( + generationContext, + dag, + leftID + ); + const right = this.generateExpression( + generationContext, + dag, + rightID + ); + const useParantheses = node.usedBy.length > 0; + if (useParantheses) { + return `(${left} % ${right})`; + } else { + return `${left} % ${right}`; + } + } - const functionArgs = node.dependsOn.map(arg =>this.generateExpression(generationContext, dag, arg)); + // Convert atan(y, x) to atan2(y, x) in WGSL + if (node.identifier === 'atan' && node.dependsOn.length === 2) { + const functionArgs = node.dependsOn.map(arg => + this.generateExpression(generationContext, dag, arg) + ); + return `atan2(${functionArgs.join(', ')})`; + } - if (node.identifier === 'random') { - const ctx = generationContext.shaderContext; - if (ctx === 'fragment') { - functionArgs.push('_p5FragPos.xy'); - } else if (ctx === 'vertex') { - functionArgs.push('f32(_p5VertexId)'); - } else if (ctx === 'compute') { - functionArgs.push('_p5GlobalId'); + const functionArgs = node.dependsOn.map(arg => + this.generateExpression(generationContext, dag, arg) + ); + + if (node.identifier === 'random') { + const ctx = generationContext.shaderContext; + if (ctx === 'fragment') { + functionArgs.push('_p5FragPos.xy'); + } else if (ctx === 'vertex') { + functionArgs.push('f32(_p5VertexId)'); + } else if (ctx === 'compute') { + functionArgs.push('_p5GlobalId'); + } } - } - return `${node.identifier}(${functionArgs.join(', ')})`; - } - if (node.opCode === OpCode.Binary.MEMBER_ACCESS) { - const [lID, rID] = node.dependsOn; - const lName = this.generateExpression(generationContext, dag, lID); - const rName = this.generateExpression(generationContext, dag, rID); - return `${lName}.${rName}`; - } - if (node.opCode === OpCode.Unary.SWIZZLE) { - const parentID = node.dependsOn[0]; - const parentExpr = this.generateExpression(generationContext, dag, parentID); - return `${parentExpr}.${node.swizzle}`; - } - if (node.opCode === OpCode.Binary.ARRAY_ACCESS) { - const [bufferID, indexID] = node.dependsOn; - const bufferExpr = this.generateExpression(generationContext, dag, bufferID); - const indexExpr = this.generateExpression(generationContext, dag, indexID); - const fieldSuffix = node.identifier ? `.${node.identifier}` : ''; - return `${bufferExpr}[i32(${indexExpr})]${fieldSuffix}`; - } - if (node.dependsOn.length === 2) { - const [lID, rID] = node.dependsOn; - const left = this.generateExpression(generationContext, dag, lID); - const right = this.generateExpression(generationContext, dag, rID); - - // In WGSL, % operator works for both floats and integers - if (node.opCode === OpCode.Binary.MODULO) { - return `(${left} % ${right})`; + return `${node.identifier}(${functionArgs.join(', ')})`; + } + if (node.opCode === OpCode.Binary.MEMBER_ACCESS) { + const [lID, rID] = node.dependsOn; + const lName = this.generateExpression(generationContext, dag, lID); + const rName = this.generateExpression(generationContext, dag, rID); + return `${lName}.${rName}`; + } + if (node.opCode === OpCode.Unary.SWIZZLE) { + const parentID = node.dependsOn[0]; + const parentExpr = this.generateExpression( + generationContext, + dag, + parentID + ); + return `${parentExpr}.${node.swizzle}`; } + if (node.opCode === OpCode.Binary.ARRAY_ACCESS) { + const [bufferID, indexID] = node.dependsOn; + const bufferExpr = this.generateExpression( + generationContext, + dag, + bufferID + ); + const indexExpr = this.generateExpression( + generationContext, + dag, + indexID + ); + const fieldSuffix = node.identifier ? `.${node.identifier}` : ''; + return `${bufferExpr}[i32(${indexExpr})]${fieldSuffix}`; + } + if (node.dependsOn.length === 2) { + const [lID, rID] = node.dependsOn; + const left = this.generateExpression(generationContext, dag, lID); + const right = this.generateExpression(generationContext, dag, rID); - const opSym = OpCodeToSymbol[node.opCode]; - if (useParantheses) { - return `(${left} ${opSym} ${right})`; - } else { - return `${left} ${opSym} ${right}`; + // In WGSL, % operator works for both floats and integers + if (node.opCode === OpCode.Binary.MODULO) { + return `(${left} % ${right})`; + } + + const opSym = OpCodeToSymbol[node.opCode]; + if (useParantheses) { + return `(${left} ${opSym} ${right})`; + } else { + return `${left} ${opSym} ${right}`; + } } - } - if (node.opCode === OpCode.Unary.LOGICAL_NOT - || node.opCode === OpCode.Unary.NEGATE - || node.opCode === OpCode.Unary.PLUS + if ( + node.opCode === OpCode.Unary.LOGICAL_NOT || + node.opCode === OpCode.Unary.NEGATE || + node.opCode === OpCode.Unary.PLUS ) { - const [i] = node.dependsOn; - const val = this.generateExpression(generationContext, dag, i); - const sym = OpCodeToSymbol[node.opCode]; - return `${sym}${val}`; - } + const [i] = node.dependsOn; + const val = this.generateExpression(generationContext, dag, i); + const sym = OpCodeToSymbol[node.opCode]; + return `${sym}${val}`; + } case NodeType.PHI: - // Phi nodes represent conditional merging of values - // If this phi node has an identifier (like varying variables), use that - if (node.identifier) { - return node.identifier; - } - // Otherwise, they should have been declared as temporary variables - // and assigned in the appropriate branches - if (generationContext.tempNames?.[nodeID]) { - return generationContext.tempNames[nodeID]; - } else { - // If no temp was created, this phi node only has one input - // so we can just use that directly - const validInputs = node.dependsOn.filter(id => id !== null); - if (validInputs.length > 0) { - return this.generateExpression(generationContext, dag, validInputs[0]); + // Phi nodes represent conditional merging of values + // If this phi node has an identifier (like varying variables), use that + if (node.identifier) { + return node.identifier; + } + // Otherwise, they should have been declared as temporary variables + // and assigned in the appropriate branches + if (generationContext.tempNames?.[nodeID]) { + return generationContext.tempNames[nodeID]; } else { - throw new Error('No valid inputs for node'); + // If no temp was created, this phi node only has one input + // so we can just use that directly + const validInputs = node.dependsOn.filter(id => id !== null); + if (validInputs.length > 0) { + return this.generateExpression( + generationContext, + dag, + validInputs[0] + ); + } else { + throw new Error('No valid inputs for node'); + } } - } case NodeType.ASSIGNMENT: - FES.internalError(`ASSIGNMENT nodes should not be used as expressions`) + FES.internalError(`ASSIGNMENT nodes should not be used as expressions`); default: - FES.internalError(`${NodeTypeToName[node.nodeType]} code generation not implemented yet`) + FES.internalError( + `${NodeTypeToName[node.nodeType]} code generation not implemented yet` + ); } }, generateBlock(blockID, strandsContext, generationContext) { @@ -605,8 +756,16 @@ export const wgslBackend = { const textureNode = getNodeDataFromID(dag, textureArg.id); const samplerIdentifier = textureNode.identifier + '_sampler'; - const samplerVariable = build.variableNode(strandsContext, { baseType: BaseType.SAMPLER, dimension: 1 }, samplerIdentifier); - const samplerNode = createStrandsNode(samplerVariable.id, samplerVariable.dimension, strandsContext); + const samplerVariable = build.variableNode( + strandsContext, + { baseType: BaseType.SAMPLER, dimension: 1 }, + samplerIdentifier + ); + const samplerNode = createStrandsNode( + samplerVariable.id, + samplerVariable.dimension, + strandsContext + ); // Create a LOD literal node (0.0) so we can use textureSampleLevel instead // of textureSample. textureSample doesn't let you use uniform values in control @@ -619,17 +778,33 @@ export const wgslBackend = { { dimension: 1, baseType: BaseType.FLOAT }, 0.0 ); - const lodNode = createStrandsNode(lodLiteral.id, lodLiteral.dimension, strandsContext); + const lodNode = createStrandsNode( + lodLiteral.id, + lodLiteral.dimension, + strandsContext + ); // Create the augmented args: [texture, sampler, coords, lod] const augmentedArgs = [textureArg, samplerNode, coordsArg, lodNode]; - const { id, dimension } = build.functionCallNode(strandsContext, 'textureSampleLevel', augmentedArgs, { - overloads: [{ - params: [DataType.sampler2D, DataType.sampler, DataType.float2, DataType.float1], - returnType: DataType.float4 - }] - }); + const { id, dimension } = build.functionCallNode( + strandsContext, + 'textureSampleLevel', + augmentedArgs, + { + overloads: [ + { + params: [ + DataType.sampler2D, + DataType.sampler, + DataType.float2, + DataType.float1 + ], + returnType: DataType.float4 + } + ] + } + ); return { id, dimension }; }, @@ -638,6 +813,11 @@ export const wgslBackend = { }, generateInstanceIDVarying() { - return { name: INSTANCE_ID_VARYING_NAME, declaration: `${INSTANCE_ID_VARYING_NAME}: i32`, source: 'i32(instanceID)', interpolation: 'flat' }; - }, -} + return { + name: INSTANCE_ID_VARYING_NAME, + declaration: `${INSTANCE_ID_VARYING_NAME}: i32`, + source: 'i32(instanceID)', + interpolation: 'flat' + }; + } +}; diff --git a/stewards.yml b/stewards.yml index 0e8b3eb1d9..2aef05b7b0 100644 --- a/stewards.yml +++ b/stewards.yml @@ -1,4 +1,3 @@ - calebfoss: - Accessibility @@ -51,7 +50,7 @@ clairep94: Nwakaego-Ego: - p5.js-website - + doradocodes: - p5.js-website - Maintainers @@ -59,7 +58,7 @@ doradocodes: coseeian: - Accessibility: - p5.js-website - + marioguzzzman: - i18n: - es @@ -77,4 +76,4 @@ RandomGamingDev: aashu2006: - Graphics: - - WebGPU \ No newline at end of file + - WebGPU diff --git a/test/bench/REPORTS/REPORT_1_TEST CPU.md b/test/bench/REPORTS/REPORT_1_TEST CPU.md index f645c2f235..22922122b4 100644 --- a/test/bench/REPORTS/REPORT_1_TEST CPU.md +++ b/test/bench/REPORTS/REPORT_1_TEST CPU.md @@ -33,7 +33,7 @@ function setup() { function draw() { background(220); - + for (const s of state) { s.pos.add(s.vel) for (const axis of ['x', 'y']) { @@ -47,7 +47,7 @@ function draw() { } } } - + const drawCircles = () => { for (const s of state) { push() @@ -71,64 +71,79 @@ function draw() { } else { drawCircles() } - + fps.html(round(frameRate())) } ``` - ### test/bench/dave.bench.js (2) 35272ms + - **Dave bench test (2) 35268ms** - **TEST CPU TRANSFORMS true**: 0.7533 ops/sec ±3.80% (10 samples) - **TEST CPU TRANSFORMS false**: 0.9418 ops/sec ±0.20% (10 samples) _fastest_ #### Summary + - **TEST CPU TRANSFORMS false** is 1.25x faster than **TEST CPU TRANSFORMS true** ### Separated + #### test/bench/dave.bench.js (2) 27912ms + - **Dave bench test (2) 27909ms** - **TEST CPU TRANSFORMS false**: 0.5350 ops/sec ±4.39% (10 samples) - **TEST CPU TRANSFORMS true**: _skipped_ #### test/bench/dave.bench.js (2) 19331ms + - **Dave bench test (2) 19327ms** - **TEST CPU TRANSFORMS false**: _skipped_ - **TEST CPU TRANSFORMS true**: 0.7519 ops/sec ±3.80% (10 samples) ### Comparing + #### DEV v2.1.5 /Users/cristianbanuelos/repos/p5.js.git/bench + - **test/bench/dave.bench.js (2) 15671ms** - **Dave bench test (2) 15667ms** - **TEST CPU TRANSFORMS false**: 0.9719 ops/sec ±1.36% (2 samples) _fastest_ - **TEST CPU TRANSFORMS true**: 0.8170 ops/sec ±15.16% (2 samples) #### Summary + - **TEST CPU TRANSFORMS false** is 1.19x faster than **TEST CPU TRANSFORMS true** #### DEV v2.1.5 /Users/cristianbanuelos/repos/p5.js.git/bench + - **test/bench/dave.bench.js (2) 15915ms** - **Dave bench test (2) 15905ms** - **TEST CPU TRANSFORMS true**: 0.7961 ops/sec ±4.86% (2 samples) - **TEST CPU TRANSFORMS false**: 0.9286 ops/sec ±36.88% (2 samples) _fastest_ #### Summary + - **TEST CPU TRANSFORMS false** is 1.17x faster than **TEST CPU TRANSFORMS true** ## With 1000 Elements + ### test/bench/dave.bench.js (2) 151509ms + - **Dave bench test (2) 151505ms** - **TEST CPU TRANSFORMS true**: 0.0582 ops/sec ±0.00% (1 sample) - **TEST CPU TRANSFORMS false**: 0.0912 ops/sec ±0.00% (1 sample) _fastest_ #### Summary + - **TEST CPU TRANSFORMS false** is 1.57x faster than **TEST CPU TRANSFORMS true** ### 1000 Elements Second Run + #### test/bench/dave.bench.js (2) 150601ms + - **Dave bench test (2) 150597ms** - **TEST CPU TRANSFORMS true**: 0.0589 ops/sec ±0.00% (1 sample) - **TEST CPU TRANSFORMS false**: 0.0914 ops/sec ±0.00% (1 sample) _fastest_ #### Summary + - **TEST CPU TRANSFORMS false** is 1.55x faster than **TEST CPU TRANSFORMS true** diff --git a/test/bench/cpu_transforms.bench.js b/test/bench/cpu_transforms.bench.js index f94b322842..edfd7aeffd 100644 --- a/test/bench/cpu_transforms.bench.js +++ b/test/bench/cpu_transforms.bench.js @@ -1,12 +1,12 @@ // import p5 from "../../../src/app.js"; -import { bench, describe } from "vitest"; -import p5 from "../../src/app"; +import { bench, describe } from 'vitest'; +import p5 from '../../src/app'; const options = { iterations: 1, time: 1500 }; -const ITERATIONS = 100 -describe.sequential("Dave bench test", () => { - bench( - "TEST CPU TRANSFORMS true", +const ITERATIONS = 100; +describe.sequential('Dave bench test', () => { + (bench( + 'TEST CPU TRANSFORMS true', async () => { try { const TEST_CPU_TRANSFORMS = true; @@ -18,7 +18,7 @@ describe.sequential("Dave bench test", () => { }); await vi.waitFor(() => { if (myp5 === undefined) { - throw new Error("not ready"); + throw new Error('not ready'); } }); let fps; @@ -31,7 +31,7 @@ describe.sequential("Dave bench test", () => { myp5.random(-200, 200), myp5.random(-200, 200) ), - vel: myp5.createVector(myp5.random(-2, 2), myp5.random(-2, 2)), + vel: myp5.createVector(myp5.random(-2, 2), myp5.random(-2, 2)) }); } fps = myp5.createP(); @@ -44,7 +44,7 @@ describe.sequential("Dave bench test", () => { for (const s of state) { s.pos.add(s.vel); - for (const axis of ["x", "y"]) { + for (const axis of ['x', 'y']) { if (s.pos[axis] < -200) { s.pos[axis] = -200; s.vel[axis] *= -1; @@ -86,9 +86,9 @@ describe.sequential("Dave bench test", () => { }, options ), - options; - bench( - "TEST CPU TRANSFORMS false", + options); + (bench( + 'TEST CPU TRANSFORMS false', async () => { const TEST_CPU_TRANSFORMS = false; var myp5; @@ -99,7 +99,7 @@ describe.sequential("Dave bench test", () => { }); await vi.waitFor(() => { if (myp5 === undefined) { - throw new Error("not ready"); + throw new Error('not ready'); } }); let fps; @@ -112,7 +112,7 @@ describe.sequential("Dave bench test", () => { myp5.random(-200, 200), myp5.random(-200, 200) ), - vel: myp5.createVector(myp5.random(-2, 2), myp5.random(-2, 2)), + vel: myp5.createVector(myp5.random(-2, 2), myp5.random(-2, 2)) }); } fps = myp5.createP(); @@ -125,7 +125,7 @@ describe.sequential("Dave bench test", () => { for (const s of state) { s.pos.add(s.vel); - for (const axis of ["x", "y"]) { + for (const axis of ['x', 'y']) { if (s.pos[axis] < -200) { s.pos[axis] = -200; s.vel[axis] *= -1; @@ -164,5 +164,5 @@ describe.sequential("Dave bench test", () => { }, options ), - options; + options); }); diff --git a/test/bench/poc.bench.js b/test/bench/poc.bench.js index 100cd2f197..6ee7e901d0 100644 --- a/test/bench/poc.bench.js +++ b/test/bench/poc.bench.js @@ -1,20 +1,20 @@ -import { bench, describe } from "vitest"; +import { bench, describe } from 'vitest'; -describe.only("sort", () => { +describe.only('sort', () => { bench( - "normal", + 'normal', () => { const x = [1, 5, 4, 2, 3]; x.sort((a, b) => { return a - b; }); - throw new Error("error"); + throw new Error('error'); }, { iterations: 100 } ); bench( - "reverse", + 'reverse', () => { const x = [1, 5, 4, 2, 3]; x.reverse() diff --git a/test/bench/rendering.bench.js b/test/bench/rendering.bench.js index 7d4813473f..c79c27653b 100644 --- a/test/bench/rendering.bench.js +++ b/test/bench/rendering.bench.js @@ -1,11 +1,11 @@ // import p5 from "../../../src/app.js"; -import { bench, describe } from "vitest"; -import p5 from "../../src/app"; +import { bench, describe } from 'vitest'; +import p5 from '../../src/app'; const options = { iterations: 20, time: 500 }; -describe("Rendering bench test", () => { +describe('Rendering bench test', () => { bench( - "normal", + 'normal', async () => { try { var myp5; @@ -17,7 +17,7 @@ describe("Rendering bench test", () => { }); await vi.waitFor(() => { if (myp5 === undefined) { - throw new Error("not ready"); + throw new Error('not ready'); } }); @@ -35,7 +35,7 @@ describe("Rendering bench test", () => { ); bench( - "thousand", + 'thousand', async () => { try { var myp5; @@ -47,7 +47,7 @@ describe("Rendering bench test", () => { }); await vi.waitFor(() => { if (myp5 === undefined) { - throw new Error("not ready"); + throw new Error('not ready'); } }); @@ -68,7 +68,7 @@ describe("Rendering bench test", () => { ); bench( - "10k", + '10k', async () => { try { var myp5; @@ -80,7 +80,7 @@ describe("Rendering bench test", () => { }); await vi.waitFor(() => { if (myp5 === undefined) { - throw new Error("not ready"); + throw new Error('not ready'); } }); @@ -101,9 +101,9 @@ describe("Rendering bench test", () => { ); }); -describe("Another suite", () => { +describe('Another suite', () => { bench( - "normal v2", + 'normal v2', async () => { try { var myp5; @@ -115,7 +115,7 @@ describe("Another suite", () => { }); await vi.waitFor(() => { if (myp5 === undefined) { - throw new Error("not ready"); + throw new Error('not ready'); } }); @@ -133,7 +133,7 @@ describe("Another suite", () => { ); bench( - "thousand v2", + 'thousand v2', async () => { try { var myp5; @@ -145,7 +145,7 @@ describe("Another suite", () => { }); await vi.waitFor(() => { if (myp5 === undefined) { - throw new Error("not ready"); + throw new Error('not ready'); } }); @@ -166,7 +166,7 @@ describe("Another suite", () => { ); bench( - "10k v2", + '10k v2', async () => { try { var myp5; @@ -178,7 +178,7 @@ describe("Another suite", () => { }); await vi.waitFor(() => { if (myp5 === undefined) { - throw new Error("not ready"); + throw new Error('not ready'); } }); diff --git a/test/bench/vectors.bench.js b/test/bench/vectors.bench.js index 952c428509..051e4710a2 100644 --- a/test/bench/vectors.bench.js +++ b/test/bench/vectors.bench.js @@ -1,120 +1,92 @@ import { Vector } from '../../src/math/p5.Vector.js'; -import { bench, describe } from "vitest"; +import { bench, describe } from 'vitest'; +describe('vector operations', () => { + bench('mult 5', () => { + const nLimited = 5; + // TODO try just operating on det. values based on i and j + // without re-creation -describe("vector operations", () => { - - bench( - "mult 5", - () => { - const nLimited = 5; - // TODO try just operating on det. values based on i and j - // without re-creation - - for (let i = 0; i < nLimited; i++) { - for (let j = 0; j < nLimited; j++) { - const tmp = arr[i].mult(arr[j]); - } + for (let i = 0; i < nLimited; i++) { + for (let j = 0; j < nLimited; j++) { + const tmp = arr[i].mult(arr[j]); } } - ); + }); + + bench('mult 10', () => { + const nLimited = 10; + const arr = setupVectors(); - bench( - "mult 10", - () => { - const nLimited = 10; - const arr = setupVectors(); - - for (let i = 0; i < nLimited; i++) { - for (let j = 0; j < nLimited; j++) { - const tmp = arr[i].mult(arr[j]); - } + for (let i = 0; i < nLimited; i++) { + for (let j = 0; j < nLimited; j++) { + const tmp = arr[i].mult(arr[j]); } } - ); + }); + + bench('mult 20', () => { + const nLimited = 20; + const arr = setupVectors(); - bench( - "mult 20", - () => { - const nLimited = 20; - const arr = setupVectors(); - - for (let i = 0; i < nLimited; i++) { - for (let j = 0; j < nLimited; j++) { - const tmp = arr[i].mult(arr[j]); - } + for (let i = 0; i < nLimited; i++) { + for (let j = 0; j < nLimited; j++) { + const tmp = arr[i].mult(arr[j]); } } - ); - bench( - "mult 100", - () => { - const nLimited = 100; - const arr = setupVectors(); - - for (let i = 0; i < nLimited; i++) { - for (let j = 0; j < nLimited; j++) { - const tmp = arr[i].mult(arr[j]); - } + }); + bench('mult 100', () => { + const nLimited = 100; + const arr = setupVectors(); + + for (let i = 0; i < nLimited; i++) { + for (let j = 0; j < nLimited; j++) { + const tmp = arr[i].mult(arr[j]); } } - ); - + }); + bench('add 5', () => { + const nLimited = 5; + const arr = setupVectors(); - bench( - "add 5", - () => { - const nLimited = 5; - const arr = setupVectors(); - - for (let i = 0; i < nLimited; i++) { - for (let j = 0; j < nLimited; j++) { - const tmp = arr[i].add(arr[j]); - } + for (let i = 0; i < nLimited; i++) { + for (let j = 0; j < nLimited; j++) { + const tmp = arr[i].add(arr[j]); } } - ); + }); - bench( - "add 10", - () => { - const nLimited = 10; - const arr = setupVectors(); - - for (let i = 0; i < nLimited; i++) { - for (let j = 0; j < nLimited; j++) { - const tmp = arr[i].add(arr[j]); - } + bench('add 10', () => { + const nLimited = 10; + const arr = setupVectors(); + + for (let i = 0; i < nLimited; i++) { + for (let j = 0; j < nLimited; j++) { + const tmp = arr[i].add(arr[j]); } - }); + } + }); + + bench('add 20', () => { + const nLimited = 20; + const arr = setupVectors(); - bench( - "add 20", - () => { - const nLimited = 20; - const arr = setupVectors(); - - for (let i = 0; i < nLimited; i++) { - for (let j = 0; j < nLimited; j++) { - const tmp = arr[i].add(arr[j]); - } + for (let i = 0; i < nLimited; i++) { + for (let j = 0; j < nLimited; j++) { + const tmp = arr[i].add(arr[j]); } } - ); - bench( - "add 100", - () => { - const nLimited = 100; - const arr = setupVectors(); - - for (let i = 0; i < nLimited; i++) { - for (let j = 0; j < nLimited; j++) { - const tmp = arr[i].add(arr[j]); - } + }); + bench('add 100', () => { + const nLimited = 100; + const arr = setupVectors(); + + for (let i = 0; i < nLimited; i++) { + for (let j = 0; j < nLimited; j++) { + const tmp = arr[i].add(arr[j]); } - }, - ); - + } + }); }); diff --git a/test/js/chai_helpers.js b/test/js/chai_helpers.js index 7018aa9c26..c2874c4eb8 100644 --- a/test/js/chai_helpers.js +++ b/test/js/chai_helpers.js @@ -9,14 +9,14 @@ assert.arrayApproximately = function (arr1, arr2, delta, desc) { for (var i = 0; i < arr1.length; i++) { assert.approximately(arr1[i], arr2[i], delta, desc); } -} +}; assert.deepCloseTo = function (actual, expected, digits = 4) { expect(actual.length).toBe(expected.length); for (let i = 0; i < actual.length; i++) { expect(actual[i]).withContext(`[${i}]`).toBeCloseTo(expected[i], digits); } -} +}; // a custom assertion for validation errors that correctly handles // minified p5 libraries. @@ -26,4 +26,4 @@ assert.validationError = function (fn) { } else { assert.doesNotThrow(fn, Error, 'got unwanted exception'); } -}; \ No newline at end of file +}; diff --git a/test/js/mocks.js b/test/js/mocks.js index 5de4e4a1ec..2a9150961d 100644 --- a/test/js/mocks.js +++ b/test/js/mocks.js @@ -8,10 +8,10 @@ const httpMocks = [ http.get('404file', () => { return new HttpResponse('Not Found', { status: 404, - statusText: 'Not Found', + statusText: 'Not Found' }); }), - http.all('*', ({request}) => { + http.all('*', ({ request }) => { return passthrough(); }) ]; @@ -32,7 +32,7 @@ Object.assign(mockP5, { const mockCanvas = document.createElement('canvas'); mockCanvas.id = 'myCanvasID'; -document.getElementsByTagName("body")[0].appendChild(mockCanvas); +document.getElementsByTagName('body')[0].appendChild(mockCanvas); export const mockP5Prototype = { saveCanvas: vi.fn(), diff --git a/test/js/modernizr.js b/test/js/modernizr.js index f86cdd8244..15479c99eb 100644 --- a/test/js/modernizr.js +++ b/test/js/modernizr.js @@ -1,5 +1,2841 @@ /*! modernizr 3.5.0 (Custom Build) | MIT * * https://modernizr.com/download/?-adownload-ambientlight-animation-apng-appearance-applicationcache-atobbtoa-audio-audioloop-audiopreload-backdropfilter-backgroundblendmode-backgroundcliptext-backgroundsize-batteryapi-bdi-beacon-bgpositionshorthand-bgpositionxy-bgrepeatspace_bgrepeatround-bgsizecover-blobconstructor-bloburls-blobworkers-borderimage-borderradius-boxshadow-boxsizing-canvas-canvasblending-canvastext-canvaswinding-capture-checked-classlist-contains-contenteditable-contextmenu-cookies-cors-cryptography-cssall-cssanimations-csscalc-csschunit-csscolumns-cssescape-cssexunit-cssfilters-cssgradients-csshyphens_softhyphens_softhyphensfind-cssinvalid-cssmask-csspointerevents-csspositionsticky-csspseudoanimations-csspseudotransitions-cssreflections-cssremunit-cssresize-cssscrollbar-csstransforms-csstransforms3d-csstransitions-cssvalid-cssvhunit-cssvmaxunit-cssvminunit-cssvwunit-cubicbezierrange-customevent-customprotocolhandler-dart-datalistelem-dataset-datauri-dataview-dataworkers-details-devicemotion_deviceorientation-directory-displaytable-documentfragment-ellipsis-emoji-es5-es5array-es5date-es5function-es5object-es5string-es5syntax-es5undefined-es6array-es6math-es6number-es6object-es6string-eventlistener-eventsource-exiforientation-fetch-fileinput-filereader-filesystem-flash-flexbox-flexboxlegacy-flexboxtweener-flexwrap-fontface-formattribute-formvalidation-framed-fullscreen-gamepads-generatedcontent-generators-geolocation-getrandomvalues-getusermedia-hashchange-hidden-hiddenscroll-history-hsla-htmlimports-ie8compat-indexeddb-indexeddbblob-inlinesvg-input-inputformaction-inputformenctype-inputformmethod-inputformtarget-inputtypes-intl-jpeg2000-jpegxr-json-lastchild-localizednumber-localstorage-lowbandwidth-lowbattery-matchmedia-mathml-mediaqueries-microdata-multiplebgs-mutationobserver-notification-nthchild-objectfit-olreversed-oninput-opacity-outputelem-overflowscrolling-pagevisibility-peerconnection-performance-picture-placeholder-pointerevents-pointerlock-postmessage-preserve3d-progressbar_meter-promises-proximity-queryselector-quotamanagement-regions-requestanimationframe-requestautocomplete-rgba-ruby-sandbox-scriptasync-scriptdefer-seamless-search-serviceworker-sessionstorage-shapes-sharedworkers-siblinggeneral-sizes-smil-speechrecognition-speechsynthesis-srcdoc-srcset-strictmode-stylescoped-subpixelfont-supports-svg-svgasimg-svgclippaths-svgfilters-svgforeignobject-target-template-templatestrings-textalignlast-textareamaxlength-textshadow-texttrackapi_track-time-todataurljpeg_todataurlpng_todataurlwebp-touchevents-transferables-typedarrays-unicode-unicoderange-unknownelements-urlparser-userdata-userselect-vibrate-video-videoautoplay-videoloop-videopreload-vml-webaudio-webgl-webglextensions-webintents-webp-webpalpha-webpanimation-websockets-websocketsbinary-websqldatabase-webworkers-willchange-wrapflow-xdomainrequest-xhr2-xhrresponsetype-xhrresponsetypearraybuffer-xhrresponsetypeblob-xhrresponsetypedocument-xhrresponsetypejson-xhrresponsetypetext-addtest-atrule-domprefixes-hasevent-mq-prefixed-prefixedcss-prefixedcssvalue-prefixes-setclasses-shiv-testallprops-testprop-teststyles !*/ -!function(window,document,undefined){function is(A,e){return typeof A===e}function testRunner(){var A,e,t,r,n,o,i;for(var d in tests)if(tests.hasOwnProperty(d)){if(A=[],e=tests[d],e.name&&(A.push(e.name.toLowerCase()),e.options&&e.options.aliases&&e.options.aliases.length))for(t=0;td;d++)if(s=A[d],l=mStyle.style[s],contains(s,"-")&&(s=cssToDOM(s)),mStyle.style[s]!==undefined){if(r||is(t,"undefined"))return n(),"pfx"==e?s:!0;try{mStyle.style[s]=t}catch(u){}if(mStyle.style[s]!=l)return n(),"pfx"==e?s:!0}return n(),!1}function fnBind(A,e){return function(){return A.apply(e,arguments)}}function testDOMProps(A,e,t){var r;for(var n in A)if(A[n]in e)return t===!1?A[n]:(r=e[A[n]],is(r,"function")?fnBind(r,t||e):r);return!1}function testPropsAll(A,e,t,r,n){var o=A.charAt(0).toUpperCase()+A.slice(1),i=(A+" "+cssomPrefixes.join(o+" ")+o).split(" ");return is(e,"string")||is(e,"undefined")?testProps(i,e,r,n):(i=(A+" "+domPrefixes.join(o+" ")+o).split(" "),testDOMProps(i,e,t))}function detectDeleteDatabase(A,e){var t=A.deleteDatabase(e);t.onsuccess=function(){addTest("indexeddb.deletedatabase",!0)},t.onerror=function(){addTest("indexeddb.deletedatabase",!1)}}function testAllProps(A,e,t){return testPropsAll(A,undefined,undefined,e,t)}var classes=[],tests=[],ModernizrProto={_version:"3.5.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(A,e){var t=this;setTimeout(function(){e(t[A])},0)},addTest:function(A,e,t){tests.push({name:A,fn:e,options:t})},addAsyncTest:function(A){tests.push({name:null,fn:A})}},Modernizr=function(){};Modernizr.prototype=ModernizrProto,Modernizr=new Modernizr,Modernizr.addTest("applicationcache","applicationCache"in window),Modernizr.addTest("blobconstructor",function(){try{return!!new Blob}catch(A){return!1}},{aliases:["blob-constructor"]}),Modernizr.addTest("cookies",function(){try{document.cookie="cookietest=1";var A=-1!=document.cookie.indexOf("cookietest=");return document.cookie="cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT",A}catch(e){return!1}}),Modernizr.addTest("cors","XMLHttpRequest"in window&&"withCredentials"in new XMLHttpRequest),Modernizr.addTest("customprotocolhandler",function(){if(!navigator.registerProtocolHandler)return!1;try{navigator.registerProtocolHandler("thisShouldFail")}catch(A){return A instanceof TypeError}return!1}),Modernizr.addTest("customevent","CustomEvent"in window&&"function"==typeof window.CustomEvent),Modernizr.addTest("dataview","undefined"!=typeof DataView&&"getFloat64"in DataView.prototype),Modernizr.addTest("eventlistener","addEventListener"in window),Modernizr.addTest("geolocation","geolocation"in navigator),Modernizr.addTest("history",function(){var A=navigator.userAgent;return-1===A.indexOf("Android 2.")&&-1===A.indexOf("Android 4.0")||-1===A.indexOf("Mobile Safari")||-1!==A.indexOf("Chrome")||-1!==A.indexOf("Windows Phone")||"file:"===location.protocol?window.history&&"pushState"in window.history:!1}),Modernizr.addTest("ie8compat",!window.addEventListener&&!!document.documentMode&&7===document.documentMode),Modernizr.addTest("json","JSON"in window&&"parse"in JSON&&"stringify"in JSON),Modernizr.addTest("notification",function(){if(!window.Notification||!window.Notification.requestPermission)return!1;if("granted"===window.Notification.permission)return!0;try{new window.Notification("")}catch(A){if("TypeError"===A.name)return!1}return!0}),Modernizr.addTest("postmessage","postMessage"in window),Modernizr.addTest("queryselector","querySelector"in document&&"querySelectorAll"in document),Modernizr.addTest("serviceworker","serviceWorker"in navigator),Modernizr.addTest("svg",!!document.createElementNS&&!!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect),Modernizr.addTest("templatestrings",function(){var supports;try{eval("``"),supports=!0}catch(e){}return!!supports}),Modernizr.addTest("typedarrays","ArrayBuffer"in window);var supports=!1;try{supports="WebSocket"in window&&2===window.WebSocket.CLOSING}catch(e){}Modernizr.addTest("websockets",supports),Modernizr.addTest("xdomainrequest","XDomainRequest"in window),Modernizr.addTest("webaudio",function(){var A="webkitAudioContext"in window,e="AudioContext"in window;return Modernizr._config.usePrefixes?A||e:e});var CSS=window.CSS;Modernizr.addTest("cssescape",CSS?"function"==typeof CSS.escape:!1);var newSyntax="CSS"in window&&"supports"in window.CSS,oldSyntax="supportsCSS"in window;Modernizr.addTest("supports",newSyntax||oldSyntax),Modernizr.addTest("target",function(){var A=window.document;if(!("querySelectorAll"in A))return!1;try{return A.querySelectorAll(":target"),!0}catch(e){return!1}}),Modernizr.addTest("microdata","getItems"in document),Modernizr.addTest("mutationobserver",!!window.MutationObserver||!!window.WebKitMutationObserver),Modernizr.addTest("picture","HTMLPictureElement"in window),Modernizr.addTest("es5array",function(){return!!(Array.prototype&&Array.prototype.every&&Array.prototype.filter&&Array.prototype.forEach&&Array.prototype.indexOf&&Array.prototype.lastIndexOf&&Array.prototype.map&&Array.prototype.some&&Array.prototype.reduce&&Array.prototype.reduceRight&&Array.isArray)}),Modernizr.addTest("es5date",function(){var A="2013-04-12T06:06:37.307Z",e=!1;try{e=!!Date.parse(A)}catch(t){}return!!(Date.now&&Date.prototype&&Date.prototype.toISOString&&Date.prototype.toJSON&&e)}),Modernizr.addTest("es5function",function(){return!(!Function.prototype||!Function.prototype.bind)}),Modernizr.addTest("es5object",function(){return!!(Object.keys&&Object.create&&Object.getPrototypeOf&&Object.getOwnPropertyNames&&Object.isSealed&&Object.isFrozen&&Object.isExtensible&&Object.getOwnPropertyDescriptor&&Object.defineProperty&&Object.defineProperties&&Object.seal&&Object.freeze&&Object.preventExtensions)}),Modernizr.addTest("strictmode",function(){"use strict";return!this}()),Modernizr.addTest("es5string",function(){return!(!String.prototype||!String.prototype.trim)}),Modernizr.addTest("es5syntax",function(){var value,obj,stringAccess,getter,setter,reservedWords,zeroWidthChars;try{return stringAccess=eval('"foobar"[3] === "b"'),getter=eval("({ get x(){ return 1 } }).x === 1"),eval("({ set x(v){ value = v; } }).x = 1"),setter=1===value,eval("obj = ({ if: 1 })"),reservedWords=1===obj["if"],zeroWidthChars=eval("_‌‍ = true"),stringAccess&&getter&&setter&&reservedWords&&zeroWidthChars}catch(ignore){return!1}}),Modernizr.addTest("es5undefined",function(){var A,e;try{e=window.undefined,window.undefined=12345,A="undefined"==typeof window.undefined,window.undefined=e}catch(t){return!1}return A}),Modernizr.addTest("es5",function(){return!!(Modernizr.es5array&&Modernizr.es5date&&Modernizr.es5function&&Modernizr.es5object&&Modernizr.strictmode&&Modernizr.es5string&&Modernizr.json&&Modernizr.es5syntax&&Modernizr.es5undefined)}),Modernizr.addTest("es6array",!!(Array.prototype&&Array.prototype.copyWithin&&Array.prototype.fill&&Array.prototype.find&&Array.prototype.findIndex&&Array.prototype.keys&&Array.prototype.entries&&Array.prototype.values&&Array.from&&Array.of)),Modernizr.addTest("generators",function(){try{new Function("function* test() {}")()}catch(A){return!1}return!0}),Modernizr.addTest("es6math",!!(Math&&Math.clz32&&Math.cbrt&&Math.imul&&Math.sign&&Math.log10&&Math.log2&&Math.log1p&&Math.expm1&&Math.cosh&&Math.sinh&&Math.tanh&&Math.acosh&&Math.asinh&&Math.atanh&&Math.hypot&&Math.trunc&&Math.fround)),Modernizr.addTest("es6number",!!(Number.isFinite&&Number.isInteger&&Number.isSafeInteger&&Number.isNaN&&Number.parseInt&&Number.parseFloat&&Number.isInteger(Number.MAX_SAFE_INTEGER)&&Number.isInteger(Number.MIN_SAFE_INTEGER)&&Number.isFinite(Number.EPSILON))),Modernizr.addTest("es6string",!!(String.fromCodePoint&&String.raw&&String.prototype.codePointAt&&String.prototype.repeat&&String.prototype.startsWith&&String.prototype.endsWith&&String.prototype.includes)),Modernizr.addTest("promises",function(){return"Promise"in window&&"resolve"in window.Promise&&"reject"in window.Promise&&"all"in window.Promise&&"race"in window.Promise&&function(){var A;return new window.Promise(function(e){A=e}),"function"==typeof A}()}),Modernizr.addTest("devicemotion","DeviceMotionEvent"in window),Modernizr.addTest("deviceorientation","DeviceOrientationEvent"in window),Modernizr.addTest("es6object",!!(Object.assign&&Object.is&&Object.setPrototypeOf)),Modernizr.addTest("filereader",!!(window.File&&window.FileList&&window.FileReader)),Modernizr.addTest("beacon","sendBeacon"in navigator),Modernizr.addTest("lowbandwidth",function(){var A=navigator.connection||{type:0};return 3==A.type||4==A.type||/^[23]g$/.test(A.type)}),Modernizr.addTest("eventsource","EventSource"in window),Modernizr.addTest("fetch","fetch"in window),Modernizr.addTest("xhrresponsetype",function(){if("undefined"==typeof XMLHttpRequest)return!1;var A=new XMLHttpRequest;return A.open("get","/",!0),"response"in A}()),Modernizr.addTest("xhr2","XMLHttpRequest"in window&&"withCredentials"in new XMLHttpRequest),Modernizr.addTest("speechsynthesis","SpeechSynthesisUtterance"in window),Modernizr.addTest("localstorage",function(){var A="modernizr";try{return localStorage.setItem(A,A),localStorage.removeItem(A),!0}catch(e){return!1}}),Modernizr.addTest("sessionstorage",function(){var A="modernizr";try{return sessionStorage.setItem(A,A),sessionStorage.removeItem(A),!0}catch(e){return!1}}),Modernizr.addTest("websqldatabase","openDatabase"in window),Modernizr.addTest("svgfilters",function(){var A=!1;try{A="SVGFEColorMatrixElement"in window&&2==SVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_SATURATE}catch(e){}return A}),Modernizr.addTest("urlparser",function(){var A;try{return A=new URL("http://modernizr.com/"),"http://modernizr.com/"===A.href}catch(e){return!1}}),Modernizr.addTest("websocketsbinary",function(){var A,e="https:"==location.protocol?"wss":"ws";if("WebSocket"in window){if(A="binaryType"in WebSocket.prototype)return A;try{return!!new WebSocket(e+"://.").binaryType}catch(t){}}return!1}),Modernizr.addTest("atobbtoa","atob"in window&&"btoa"in window,{aliases:["atob-btoa"]}),Modernizr.addTest("framed",window.location!=top.location),Modernizr.addTest("sharedworkers","SharedWorker"in window),Modernizr.addTest("webworkers","Worker"in window);var prefixes=ModernizrProto._config.usePrefixes?" -webkit- -moz- -o- -ms- ".split(" "):["",""];ModernizrProto._prefixes=prefixes,Modernizr.addTest("contains",is(String.prototype.contains,"function"));var docElement=document.documentElement;Modernizr.addTest("contextmenu","contextMenu"in docElement&&"HTMLMenuItemElement"in window),Modernizr.addTest("cssall","all"in docElement.style),Modernizr.addTest("willchange","willChange"in docElement.style),Modernizr.addTest("classlist","classList"in docElement),Modernizr.addTest("documentfragment",function(){return"createDocumentFragment"in document&&"appendChild"in docElement});var isSVG="svg"===docElement.nodeName.toLowerCase(),html5;isSVG||!function(A,e){function t(A,e){var t=A.createElement("p"),r=A.getElementsByTagName("head")[0]||A.documentElement;return t.innerHTML="x",r.insertBefore(t.lastChild,r.firstChild)}function r(){var A=h.elements;return"string"==typeof A?A.split(" "):A}function n(A,e){var t=h.elements;"string"!=typeof t&&(t=t.join(" ")),"string"!=typeof A&&(A=A.join(" ")),h.elements=t+" "+A,s(e)}function o(A){var e=E[A[f]];return e||(e={},g++,A[f]=g,E[g]=e),e}function i(A,t,r){if(t||(t=e),c)return t.createElement(A);r||(r=o(t));var n;return n=r.cache[A]?r.cache[A].cloneNode():m.test(A)?(r.cache[A]=r.createElem(A)).cloneNode():r.createElem(A),!n.canHaveChildren||w.test(A)||n.tagUrn?n:r.frag.appendChild(n)}function d(A,t){if(A||(A=e),c)return A.createDocumentFragment();t=t||o(A);for(var n=t.frag.cloneNode(),i=0,d=r(),a=d.length;a>i;i++)n.createElement(d[i]);return n}function a(A,e){e.cache||(e.cache={},e.createElem=A.createElement,e.createFrag=A.createDocumentFragment,e.frag=e.createFrag()),A.createElement=function(t){return h.shivMethods?i(t,A,e):e.createElem(t)},A.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+r().join().replace(/[\w\-:]+/g,function(A){return e.createElem(A),e.frag.createElement(A),'c("'+A+'")'})+");return n}")(h,e.frag)}function s(A){A||(A=e);var r=o(A);return!h.shivCSS||l||r.hasCSS||(r.hasCSS=!!t(A,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),c||a(A,r),A}var l,c,u="3.7.3",p=A.html5||{},w=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,m=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,f="_html5shiv",g=0,E={};!function(){try{var A=e.createElement("a");A.innerHTML="",l="hidden"in A,c=1==A.childNodes.length||function(){e.createElement("a");var A=e.createDocumentFragment();return"undefined"==typeof A.cloneNode||"undefined"==typeof A.createDocumentFragment||"undefined"==typeof A.createElement}()}catch(t){l=!0,c=!0}}();var h={elements:p.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:u,shivCSS:p.shivCSS!==!1,supportsUnknownElements:c,shivMethods:p.shivMethods!==!1,type:"default",shivDocument:s,createElement:i,createDocumentFragment:d,addElements:n};A.html5=h,s(e),"object"==typeof module&&module.exports&&(module.exports=h)}("undefined"!=typeof window?window:this,document);var omPrefixes="Moz O ms Webkit",domPrefixes=ModernizrProto._config.usePrefixes?omPrefixes.toLowerCase().split(" "):[];ModernizrProto._domPrefixes=domPrefixes;var hasOwnProp;!function(){var A={}.hasOwnProperty;hasOwnProp=is(A,"undefined")||is(A.call,"undefined")?function(A,e){return e in A&&is(A.constructor.prototype[e],"undefined")}:function(e,t){return A.call(e,t)}}(),ModernizrProto._l={},ModernizrProto.on=function(A,e){this._l[A]||(this._l[A]=[]),this._l[A].push(e),Modernizr.hasOwnProperty(A)&&setTimeout(function(){Modernizr._trigger(A,Modernizr[A])},0)},ModernizrProto._trigger=function(A,e){if(this._l[A]){var t=this._l[A];setTimeout(function(){var A,r;for(A=0;An;n++){var o=prefixes[n],i=o.toUpperCase()+"_"+e;if(i in r)return"@-"+o.toLowerCase()+"-"+A}return!1};ModernizrProto.atRule=atRule;var hasEvent=function(){function A(A,t){var r;return A?(t&&"string"!=typeof t||(t=createElement(t||"div")),A="on"+A,r=A in t,!r&&e&&(t.setAttribute||(t=createElement("div")),t.setAttribute(A,""),r="function"==typeof t[A],t[A]!==undefined&&(t[A]=undefined),t.removeAttribute(A)),r):!1}var e=!("onblur"in document.documentElement);return A}();ModernizrProto.hasEvent=hasEvent,Modernizr.addTest("ambientlight",hasEvent("devicelight",window)),Modernizr.addTest("hashchange",function(){return hasEvent("hashchange",window)===!1?!1:document.documentMode===undefined||document.documentMode>7}),Modernizr.addTest("inputsearchevent",hasEvent("search")),Modernizr.addTest("pointerevents",function(){var A=!1,e=domPrefixes.length;for(A=Modernizr.hasEvent("pointerdown");e--&&!A;)hasEvent(domPrefixes[e]+"pointerdown")&&(A=!0);return A});var prefixedCSSValue=function(A,e){var t=!1,r=createElement("div"),n=r.style;if(A in n){var o=domPrefixes.length;for(n[A]=e,t=n[A];o--&&!t;)n[A]="-"+domPrefixes[o]+"-"+e,t=n[A]}return""===t&&(t=!1),t};ModernizrProto.prefixedCSSValue=prefixedCSSValue,Modernizr.addTest("audio",function(){var A=createElement("audio"),e=!1;try{e=!!A.canPlayType,e&&(e=new Boolean(e),e.ogg=A.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),e.mp3=A.canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/,""),e.opus=A.canPlayType('audio/ogg; codecs="opus"')||A.canPlayType('audio/webm; codecs="opus"').replace(/^no$/,""),e.wav=A.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),e.m4a=(A.canPlayType("audio/x-m4a;")||A.canPlayType("audio/aac;")).replace(/^no$/,""))}catch(t){}return e}),Modernizr.addTest("canvas",function(){var A=createElement("canvas");return!(!A.getContext||!A.getContext("2d"))}),Modernizr.addTest("canvastext",function(){return Modernizr.canvas===!1?!1:"function"==typeof createElement("canvas").getContext("2d").fillText}),Modernizr.addTest("contenteditable",function(){if("contentEditable"in docElement){var A=createElement("div");return A.contentEditable=!0,"true"===A.contentEditable}}),Modernizr.addTest("emoji",function(){if(!Modernizr.canvastext)return!1;var A=window.devicePixelRatio||1,e=12*A,t=createElement("canvas"),r=t.getContext("2d");return r.fillStyle="#f00",r.textBaseline="top",r.font="32px Arial",r.fillText("🐨",0,0),0!==r.getImageData(e,e,1,1).data[0]}),addTest("htmlimports","import"in createElement("link")),Modernizr.addTest("olreversed","reversed"in createElement("ol")),Modernizr.addTest("userdata",!!createElement("div").addBehavior),Modernizr.addTest("video",function(){var A=createElement("video"),e=!1;try{e=!!A.canPlayType,e&&(e=new Boolean(e),e.ogg=A.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),e.h264=A.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),e.webm=A.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,""),e.vp9=A.canPlayType('video/webm; codecs="vp9"').replace(/^no$/,""),e.hls=A.canPlayType('application/x-mpegURL; codecs="avc1.42E01E"').replace(/^no$/,""))}catch(t){}return e}),Modernizr.addTest("vml",function(){var A,e=createElement("div"),t=!1;return isSVG||(e.innerHTML='',A=e.firstChild,"style"in A&&(A.style.behavior="url(#default#VML)"),t=A?"object"==typeof A.adj:!0),t}),Modernizr.addTest("webanimations","animate"in createElement("div")),Modernizr.addTest("webgl",function(){var A=createElement("canvas"),e="probablySupportsContext"in A?"probablySupportsContext":"supportsContext";return e in A?A[e]("webgl")||A[e]("experimental-webgl"):"WebGLRenderingContext"in window}),Modernizr.addTest("adownload",!window.externalHost&&"download"in createElement("a")),Modernizr.addTest("audioloop","loop"in createElement("audio")),Modernizr.addAsyncTest(function(){function A(t){clearTimeout(e);var n=t!==undefined&&"loadeddata"===t.type?!0:!1;r.removeEventListener("loadeddata",A,!1),addTest("audiopreload",n),r.parentNode.removeChild(r)}var e,t=300,r=createElement("audio"),n=r.style;if(!(Modernizr.audio&&"preload"in r))return void addTest("audiopreload",!1);n.position="absolute",n.height=0,n.width=0;try{if(Modernizr.audio.mp3)r.src="data:audio/mpeg;base64,//MUxAAB6AXgAAAAAPP+c6nf//yi/6f3//MUxAMAAAIAAAjEcH//0fTX6C9Lf//0//MUxA4BeAIAAAAAAKX2/6zv//+IlR4f//MUxBMCMAH8AAAAABYWalVMQU1FMy45//MUxBUB0AH0AAAAADkuM1VVVVVVVVVV//MUxBgBUATowAAAAFVVVVVVVVVVVVVV";else if(Modernizr.audio.m4a)r.src="data:audio/x-m4a;base64,AAAAGGZ0eXBNNEEgAAACAGlzb21pc28yAAAACGZyZWUAAAAfbWRhdN4EAABsaWJmYWFjIDEuMjgAAAFoAQBHAAACiG1vb3YAAABsbXZoZAAAAAB8JbCAfCWwgAAAA+gAAAAYAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAG0dHJhawAAAFx0a2hkAAAAD3wlsIB8JbCAAAAAAQAAAAAAAAAYAAAAAAAAAAAAAAAAAQAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAABUG1kaWEAAAAgbWRoZAAAAAB8JbCAfCWwgAAArEQAAAQAVcQAAAAAAC1oZGxyAAAAAAAAAABzb3VuAAAAAAAAAAAAAAAAU291bmRIYW5kbGVyAAAAAPttaW5mAAAAEHNtaGQAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAL9zdGJsAAAAW3N0c2QAAAAAAAAAAQAAAEttcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAArEQAAAAAACdlc2RzAAAAAAMZAAEABBFAFQAAAAABftAAAAAABQISCAYBAgAAABhzdHRzAAAAAAAAAAEAAAABAAAEAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAAXAAAAAQAAABRzdGNvAAAAAAAAAAEAAAAoAAAAYHVkdGEAAABYbWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAraWxzdAAAACOpdG9vAAAAG2RhdGEAAAABAAAAAExhdmY1Mi42NC4y";else if(Modernizr.audio.ogg)r.src="data:audio/ogg;base64,T2dnUwACAAAAAAAAAAD/QwAAAAAAAM2LVKsBHgF2b3JiaXMAAAAAAUSsAAAAAAAAgLsAAAAAAAC4AU9nZ1MAAAAAAAAAAAAA/0MAAAEAAADmvOe6Dy3/////////////////MgN2b3JiaXMdAAAAWGlwaC5PcmcgbGliVm9yYmlzIEkgMjAwNzA2MjIAAAAAAQV2b3JiaXMfQkNWAQAAAQAYY1QpRplS0kqJGXOUMUaZYpJKiaWEFkJInXMUU6k515xrrLm1IIQQGlNQKQWZUo5SaRljkCkFmVIQS0kldBI6J51jEFtJwdaYa4tBthyEDZpSTCnElFKKQggZU4wpxZRSSkIHJXQOOuYcU45KKEG4nHOrtZaWY4updJJK5yRkTEJIKYWSSgelU05CSDWW1lIpHXNSUmpB6CCEEEK2IIQNgtCQVQAAAQDAQBAasgoAUAAAEIqhGIoChIasAgAyAAAEoCiO4iiOIzmSY0kWEBqyCgAAAgAQAADAcBRJkRTJsSRL0ixL00RRVX3VNlVV9nVd13Vd13UgNGQVAAABAEBIp5mlGiDCDGQYCA1ZBQAgAAAARijCEANCQ1YBAAABAABiKDmIJrTmfHOOg2Y5aCrF5nRwItXmSW4q5uacc845J5tzxjjnnHOKcmYxaCa05pxzEoNmKWgmtOacc57E5kFrqrTmnHPGOaeDcUYY55xzmrTmQWo21uaccxa0pjlqLsXmnHMi5eZJbS7V5pxzzjnnnHPOOeecc6oXp3NwTjjnnHOi9uZabkIX55xzPhmne3NCOOecc84555xzzjnnnHOC0JBVAAAQAABBGDaGcacgSJ+jgRhFiGnIpAfdo8MkaAxyCqlHo6ORUuoglFTGSSmdIDRkFQAACAAAIYQUUkghhRRSSCGFFFKIIYYYYsgpp5yCCiqppKKKMsoss8wyyyyzzDLrsLPOOuwwxBBDDK20EktNtdVYY62555xrDtJaaa211koppZRSSikIDVkFAIAAABAIGWSQQUYhhRRSiCGmnHLKKaigAkJDVgEAgAAAAgAAADzJc0RHdERHdERHdERHdETHczxHlERJlERJtEzL1ExPFVXVlV1b1mXd9m1hF3bd93Xf93Xj14VhWZZlWZZlWZZlWZZlWZZlWYLQkFUAAAgAAIAQQgghhRRSSCGlGGPMMeegk1BCIDRkFQAACAAgAAAAwFEcxXEkR3IkyZIsSZM0S7M8zdM8TfREURRN01RFV3RF3bRF2ZRN13RN2XRVWbVdWbZt2dZtX5Zt3/d93/d93/d93/d93/d1HQgNWQUASAAA6EiOpEiKpEiO4ziSJAGhIasAABkAAAEAKIqjOI7jSJIkSZakSZ7lWaJmaqZneqqoAqEhqwAAQAAAAQAAAAAAKJriKabiKaLiOaIjSqJlWqKmaq4om7Lruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7rui4QGrIKAJAAANCRHMmRHEmRFEmRHMkBQkNWAQAyAAACAHAMx5AUybEsS9M8zdM8TfRET/RMTxVd0QVCQ1YBAIAAAAIAAAAAADAkw1IsR3M0SZRUS7VUTbVUSxVVT1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTVN0zRNIDRkJQAABADAYo3B5SAhJSXl3hDCEJOeMSYhtV4hBJGS3jEGFYOeMqIMct5C4xCDHggNWREARAEAAMYgxxBzyDlHqZMSOeeodJQa5xyljlJnKcWYYs0oldhSrI1zjlJHraOUYiwtdpRSjanGAgAAAhwAAAIshEJDVgQAUQAAhDFIKaQUYow5p5xDjCnnmHOGMeYcc44556B0UirnnHROSsQYc445p5xzUjonlXNOSiehAACAAAcAgAALodCQFQFAnACAQZI8T/I0UZQ0TxRFU3RdUTRd1/I81fRMU1U90VRVU1Vt2VRVWZY8zzQ901RVzzRV1VRVWTZVVZZFVdVt03V123RV3ZZt2/ddWxZ2UVVt3VRd2zdV1/Zd2fZ9WdZ1Y/I8VfVM03U903Rl1XVtW3VdXfdMU5ZN15Vl03Vt25VlXXdl2fc103Rd01Vl2XRd2XZlV7ddWfZ903WF35VlX1dlWRh2XfeFW9eV5XRd3VdlVzdWWfZ9W9eF4dZ1YZk8T1U903RdzzRdV3VdX1dd19Y105Rl03Vt2VRdWXZl2fddV9Z1zzRl2XRd2zZdV5ZdWfZ9V5Z13XRdX1dlWfhVV/Z1WdeV4dZt4Tdd1/dVWfaFV5Z14dZ1Ybl1XRg+VfV9U3aF4XRl39eF31luXTiW0XV9YZVt4VhlWTl+4ViW3feVZXRdX1ht2RhWWRaGX/id5fZ943h1XRlu3efMuu8Mx++k+8rT1W1jmX3dWWZfd47hGDq/8OOpqq+brisMpywLv+3rxrP7vrKMruv7qiwLvyrbwrHrvvP8vrAso+z6wmrLwrDatjHcvm4sv3Acy2vryjHrvlG2dXxfeArD83R1XXlmXcf2dXTjRzh+ygAAgAEHAIAAE8pAoSErAoA4AQCPJImiZFmiKFmWKIqm6LqiaLqupGmmqWmeaVqaZ5qmaaqyKZquLGmaaVqeZpqap5mmaJqua5qmrIqmKcumasqyaZqy7LqybbuubNuiacqyaZqybJqmLLuyq9uu7Oq6pFmmqXmeaWqeZ5qmasqyaZquq3meanqeaKqeKKqqaqqqraqqLFueZ5qa6KmmJ4qqaqqmrZqqKsumqtqyaaq2bKqqbbuq7Pqybeu6aaqybaqmLZuqatuu7OqyLNu6L2maaWqeZ5qa55mmaZqybJqqK1uep5qeKKqq5ommaqqqLJumqsqW55mqJ4qq6omea5qqKsumatqqaZq2bKqqLZumKsuubfu+68qybqqqbJuqauumasqybMu+78qq7oqmKcumqtqyaaqyLduy78uyrPuiacqyaaqybaqqLsuybRuzbPu6aJqybaqmLZuqKtuyLfu6LNu678qub6uqrOuyLfu67vqucOu6MLyybPuqrPq6K9u6b+sy2/Z9RNOUZVM1bdtUVVl2Zdn2Zdv2fdE0bVtVVVs2TdW2ZVn2fVm2bWE0Tdk2VVXWTdW0bVmWbWG2ZeF2Zdm3ZVv2ddeVdV/XfePXZd3murLty7Kt+6qr+rbu+8Jw667wCgAAGHAAAAgwoQwUGrISAIgCAACMYYwxCI1SzjkHoVHKOecgZM5BCCGVzDkIIZSSOQehlJQy5yCUklIIoZSUWgshlJRSawUAABQ4AAAE2KApsThAoSErAYBUAACD41iW55miatqyY0meJ4qqqaq27UiW54miaaqqbVueJ4qmqaqu6+ua54miaaqq6+q6aJqmqaqu67q6Lpqiqaqq67qyrpumqqquK7uy7Oumqqqq68quLPvCqrquK8uybevCsKqu68qybNu2b9y6ruu+7/vCka3rui78wjEMRwEA4AkOAEAFNqyOcFI0FlhoyEoAIAMAgDAGIYMQQgYhhJBSSiGllBIAADDgAAAQYEIZKDRkRQAQJwAAGEMppJRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkgppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkqppJRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoplVJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSCgCQinAAkHowoQwUGrISAEgFAACMUUopxpyDEDHmGGPQSSgpYsw5xhyUklLlHIQQUmktt8o5CCGk1FJtmXNSWosx5hgz56SkFFvNOYdSUoux5ppr7qS0VmuuNedaWqs115xzzbm0FmuuOdecc8sx15xzzjnnGHPOOeecc84FAOA0OACAHtiwOsJJ0VhgoSErAYBUAAACGaUYc8456BBSjDnnHIQQIoUYc845CCFUjDnnHHQQQqgYc8w5CCGEkDnnHIQQQgghcw466CCEEEIHHYQQQgihlM5BCCGEEEooIYQQQgghhBA6CCGEEEIIIYQQQgghhFJKCCGEEEIJoZRQAABggQMAQIANqyOcFI0FFhqyEgAAAgCAHJagUs6EQY5Bjw1BylEzDUJMOdGZYk5qMxVTkDkQnXQSGWpB2V4yCwAAgCAAIMAEEBggKPhCCIgxAABBiMwQCYVVsMCgDBoc5gHAA0SERACQmKBIu7iALgNc0MVdB0IIQhCCWBxAAQk4OOGGJ97whBucoFNU6iAAAAAAAAwA4AEA4KAAIiKaq7C4wMjQ2ODo8AgAAAAAABYA+AAAOD6AiIjmKiwuMDI0Njg6PAIAAAAAAAAAAICAgAAAAAAAQAAAAICAT2dnUwAE7AwAAAAAAAD/QwAAAgAAADuydfsFAQEBAQEACg4ODg==";else{ -if(!Modernizr.audio.wav)return void addTest("audiopreload",!1);r.src="data:audio/wav;base64,UklGRvwZAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YdgZAAAAAAEA/v8CAP//AAABAP////8DAPz/BAD9/wEAAAAAAAAAAAABAP7/AgD//wAAAQD//wAAAQD//wAAAQD+/wIA//8AAAAAAAD//wIA/v8BAAAA//8BAAAA//8BAP//AQAAAP//AQD//wEAAAD//wEA//8BAP//AQD//wEA//8BAP//AQD+/wMA/f8DAP3/AgD+/wIA/////wMA/f8CAP7/AgD+/wMA/f8CAP7/AgD//wAAAAAAAAAAAQD+/wIA/v8CAP7/AwD9/wIA/v8BAAEA/v8CAP7/AQAAAAAAAAD//wEAAAD//wIA/f8DAP7/AQD//wEAAAD//wEA//8CAP7/AQD//wIA/v8CAP7/AQAAAAAAAAD//wEAAAAAAAAA//8BAP//AgD9/wQA+/8FAPz/AgAAAP//AgD+/wEAAAD//wIA/v8CAP3/BAD8/wQA/P8DAP7/AwD8/wQA/P8DAP7/AQAAAAAA//8BAP//AgD+/wEAAAD//wIA/v8BAP//AQD//wEAAAD//wEA//8BAAAAAAAAAP//AgD+/wEAAAAAAAAAAAD//wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AgD+/wIA/v8BAP//AQABAP7/AQD//wIA/v8CAP3/AwD/////AgD9/wMA/v8BAP//AQAAAP//AQD//wEA//8BAP//AAABAP//AAABAP//AQD//wAAAAACAP3/AwD9/wIA//8BAP//AQD//wEA//8BAP//AgD9/wMA/v8AAAIA/f8CAAAA/v8EAPv/BAD9/wIAAAD+/wQA+v8HAPr/BAD+/wEAAAD//wIA/f8EAPz/BAD7/wUA/P8EAPz/AwD+/wEAAAD//wEAAAAAAP//AgD8/wUA+/8FAPz/AwD9/wIA//8AAAEA/v8CAP//AQD//wAAAAABAP//AgD9/wMA/f8EAPz/AwD+/wAAAwD7/wUA/P8DAP7/AQAAAP//AgD+/wEAAQD+/wIA/v8BAAEA/v8CAP7/AQAAAP//AgD9/wMA/f8DAP7/AgD+/wEAAAAAAAEA//8AAAEA/v8DAP3/AgD//wEA//8BAP7/AwD9/wMA/v8BAP//AQAAAP//AgD9/wMA/v8BAP//AQAAAP//AgD+/wEAAQD+/wIA/////wIA//8AAAEA/f8DAP//AAABAP////8DAP3/AwD+/wEA//8BAP//AQAAAAAA//8BAP//AQD//wEA//8BAP//AAAAAAEA//8BAP7/AgD//wEA//8AAAAAAAAAAAAAAAD//wIA/v8BAAAA//8BAAEA/v8BAAAA//8DAPz/AwD+/wIA/v8CAP3/AwD+/wEAAAD//wEA//8BAAAA//8BAAAA/v8EAPv/BAD+/wAAAAABAP7/AgD//wAAAAABAP7/AgD//wAAAAAAAAAAAAABAP3/BAD8/wQA/f8BAAAAAAABAP7/AgD+/wIA/v8CAP7/AgD+/wIA/v8BAAAAAAD//wIA/f8DAP7/AAABAP//AAACAPz/BAD9/wIA//8AAP//AwD9/wMA/P8EAP3/AwD9/wIA//8BAP//AQD+/wMA/f8DAP7/AAABAP//AQAAAP//AQD//wIA/f8DAP7/AQAAAP//AQAAAAAA//8CAP7/AQABAP7/AgD+/wEAAQD+/wIA/v8CAP////8CAP7/AgD//wAAAAABAP7/AwD9/wIAAAD+/wMA/f8CAP//AQD+/wMA/f8CAP//AAACAPz/BQD6/wUA/v///wIA/v8CAP3/BAD7/wYA+v8FAPz/AwD/////AgD+/wEAAAD//wEAAAD//wIA/f8DAP7/AQAAAP//AgD//wAA//8BAAAAAAAAAP//AQD//wEA//8AAAIA/f8DAP3/AgAAAP//AQD//wEA//8AAAEA//8BAP////8CAP//AAABAP3/BAD9/wIA/v8BAAEA//8BAP7/AgD//wEA//8AAAEA//8BAP//AAAAAAEA//8BAP7/AgD//wEA//8AAAAAAQD+/wIA/v8BAAAAAAD//wIA/v8BAAAAAAAAAAAAAQD+/wMA/f8CAP//AQD//wIA/f8DAP7/AQD//wEA//8CAP7/AAABAP7/AwD9/wMA/v8AAAEA//8BAAAAAAD//wIA/v8BAAAA//8CAP7/AgD+/wEA//8CAP7/AgD//wAAAAAAAAAAAQD//wEA/v8DAPz/BQD8/wIA//8AAAEAAAD//wEA//8BAP//AQAAAAAA//8BAP//AgD+/wEAAAAAAP//AQD+/wMA/////wEA/v8CAP//AQD//wEA//8AAAEA//8BAAAA/v8EAPz/AwD+/wEAAAAAAAAA//8CAP7/AQD//wEA//8BAP//AAABAP7/AwD9/wIA//8BAP//AQD//wEA//8AAAEA/v8EAPv/BAD9/wIA//8BAP7/AwD9/wIA//8AAAEA//8BAP//AQD//wAAAQD//wEAAAD+/wMA/v8AAAIA/f8DAP7/AQD//wAAAQD+/wMA/f8CAP//AAABAP7/AgD+/wMA/f8CAP7/AQABAP7/AgD+/wIA/v8CAP7/AwD8/wMA//8AAAEA//8AAAAAAAABAP//AQD//wAAAQD//wIA/f8DAP3/AwD+/wAAAgD9/wIA//8AAAEAAAD+/wMA/P8FAPv/BAD9/wIA//8AAP//AgD+/wIA/v8BAAAAAAD//wEAAAAAAP//AQD//wEA//8BAP//AAABAP7/AwD9/wIA//8BAP//AAABAP//AQD//wAAAQD//wEA//8BAP//AAABAAAA//8BAP7/AwD9/wMA/f8DAP3/AgD//wEA//8BAP7/AgD//wAAAgD8/wQA/f8CAP//AQD+/wMA/f8CAP7/AgD//wAAAAAAAAAAAAABAP7/AwD9/wIA/v8DAP3/AwD9/wIA/v8DAPz/BQD7/wQA/f8CAP7/AwD9/wMA/f8CAP//AQAAAP7/AwD+/wEA//8AAAEAAAAAAP//AAABAP//AQAAAP7/AwD9/wMA/f8CAP//AQD//wEA//8AAAIA/f8CAAAA//8BAAAA//8BAAAA/v8EAPv/BAD9/wIA//8AAAEA/v8CAP//AAABAP//AAABAP//AAABAP7/AwD8/wQA/f8CAAAA/v8DAP3/AwD9/wMA/v8BAAAA//8BAAAA//8CAP7/AQAAAAAAAAAAAAAA//8CAP7/AgD+/wIA/v8CAP7/AgD//wAAAQD//wAAAQD//wAAAQD//wAAAQD+/wIA//8AAAAAAQD+/wMA/f8CAP//AQD//wEA//8AAAEA/v8DAP3/AgD//wAAAAABAP7/AwD9/wIA//8AAAEA/v8DAP3/AgD//wAAAAABAP7/AwD8/wMA/v8CAP//AAD//wIA/v8CAP7/AQABAP7/AQAAAP//AgD/////AQD//wEAAAD//wEA/v8EAPv/BAD9/wMA/v8BAAAA//8BAAEA/P8GAPr/BQD8/wMA/v8BAAAA//8CAP7/AQABAP3/BAD7/wYA+/8EAPz/AwD//wEA//8BAP7/BAD8/wMA/v8AAAIA/v8BAAAA//8BAAAA//8BAAAA//8CAP3/AwD+/wAAAgD8/wUA/P8DAP7/AAABAAAAAAD//wEAAAD//wIA/f8DAP7/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA/f8EAPz/AwD/////AgD+/wIA/f8DAP7/AgD+/wEA//8CAP7/AQD//wEAAAAAAP//AQAAAP//AgD9/wMA/v8BAAAA//8BAP//AQAAAP//AAACAP3/BAD7/wQA/v8BAAAA//8BAP//AQAAAP//AQAAAP7/BAD7/wUA+/8EAP3/AgD//wAAAQD+/wIA//8AAAEA/v8CAP//AQD+/wEAAAAAAAAAAAD//wEA//8CAP3/AwD9/wIA//8AAAAAAAAAAAAA//8BAP//AgD+/wEA//8CAP7/AQAAAP//AgD/////AgD/////AgD+/wIA//8AAP//AQABAP7/AgD9/wMA/v8CAP////8BAAAAAAAAAAAA//8CAP////8DAPz/AwD+/wEAAAAAAP//AQD//wEAAAD//wEAAAD+/wQA+/8FAPz/AgAAAP//AgD9/wMA/v8BAAAAAAD//wEAAAD//wIA/v8BAAAAAAD//wIA/v8BAAAA//8BAAAA//8CAP7/AQD//wEA//8BAAAA//8BAP//AAABAP//AQAAAP7/AgD//wEA//8AAAAAAQD+/wMA/P8EAP7///8DAPz/BQD8/wEAAQD+/wMA/v8AAAEA//8BAP//AQD//wEA/v8CAP//AQD//wAAAAABAAAA//8BAP//AQAAAAAA//8BAP//AgD+/wAAAQD//wIA/f8CAP//AQAAAP7/AwD9/wMA/v8BAP//AAABAP//AgD9/wIA//8BAAAA//8BAAAA//8CAP3/AwD+/wEAAAD+/wQA/P8DAP7/AAACAP7/AQAAAP//AQAAAP//AQAAAP//AgD9/wIAAAD//wIA/f8DAP7/AQD//wEA//8CAP7/AQD//wAAAQD//wEA//8AAAAAAQD//wEAAAD9/wUA+/8FAPz/AgD//wAAAQD//wAAAQD+/wMA/f8BAAEA/v8CAP7/AgD+/wIA/v8BAAAAAAAAAAAAAAD//wIA/v8CAP////8CAP7/AgD+/wIA/v8CAP7/AQAAAP//AQAAAP//AQD//wAAAQD//wAAAQD+/wMA/f8CAAAA/v8DAP3/AgAAAP//AQAAAP7/AwD9/wMA/v8BAP//AQD//wEAAAD+/wMA/f8CAAAA/v8CAP//AAAAAAEA//8AAAEA/v8DAP3/AwD9/wIA//8BAP//AgD8/wQA/v8BAAAA/v8CAP//AQD//wAAAAAAAAEA/f8EAPz/BAD9/wIA//8AAAAAAAABAP//AAAAAAAAAAABAP3/BAD9/wIA/v8BAAEA//8AAAAA//8CAP7/AgD9/wQA+/8FAPv/BQD8/wMA/f8DAP3/AwD+/wAAAgD9/wMA/f8CAAAA/v8EAPv/BQD7/wUA/P8DAP///v8DAP3/BAD8/wMA/f8DAP7/AQD//wEAAAD//wEA/v8CAAAA/v8CAP7/AgD//wAAAAAAAAAAAQD+/wIA//8AAAEA/v8DAPz/BAD9/wIA//8AAP//AgD//wEA/v8BAAAAAQD//wAAAAAAAAEA//8AAAEA//8BAP//AAABAP//AQD+/wIA/v8DAPz/BAD8/wQA/f8BAAAAAQD+/wMA/P8DAP//AAAAAAAAAAD//wMA+/8FAP3/AQABAP3/BAD8/wMA/v8BAAAA//8CAP3/AwD+/wEAAQD9/wMA/f8EAPz/BAD7/wQA/v8BAAEA/f8DAP7/AQAAAP//AgD+/wEAAAD//wIA/v8CAP7/AgD+/wEAAQD//wEA/v8CAP7/BAD7/wQA/f8CAAAA//8AAAAAAAABAP//AQD+/wEAAQD+/wMA/f8BAAEA/v8DAPz/AwD/////AwD8/wQA/P8DAP7/AgD//wAA//8BAAAAAAAAAP//AgD+/wEAAAD//wIA/v8BAAAA//8CAP3/AgD//wAAAQD+/wIA/v8BAAAA//8CAP7/AgD+/wEA//8CAP3/BAD7/wQA/v8BAAAA//8AAAEAAAD//wIA/f8DAP7/AgD+/wIA/v8CAP7/AgD+/wEAAAAAAP//AgD9/wMA/v8BAP//AgD9/wMA/v8AAAEA//8BAP//AQD//wEA//8AAAEA/v8EAPz/AgD//wAAAQAAAP//AAABAP//AQD//wEAAAD//wEA//8BAAEA/f8DAP7/AQABAP3/AwD+/wIA/////wEAAAAAAAAAAAD//wIA/v8CAP////8CAP7/AgD//wAA//8CAP3/BAD9/wAAAgD9/wMA/v8BAP//AQAAAP//AQAAAP//AgD9/wMA/f8EAPz/AwD+/wEAAAAAAAAAAAD//wIA/f8EAP3/AAABAAAA//8CAP7/AQAAAP//AQAAAAAA//8BAP//AQAAAP//AQAAAP//AQAAAP//AgD9/wMA/v8BAP//AQAAAP//AQD//wIA/v8CAP3/BAD9/wEAAAD//wEAAQD9/wMA/f8CAAAA/v8DAP3/AgD//wAAAQD+/wIA/v8CAP7/AQAAAP//AgD+/wEAAAAAAP//AwD7/wUA/f8BAAEA/v8BAAEA/v8DAP3/AgD//wEA//8BAP//AQD//wEA//8CAP3/BAD7/wQA/////wIA/v8AAAIA/v8CAP3/BAD7/wUA/P8DAP3/AwD9/wMA/v8AAAIA/v8CAP7/AgD+/wIA//8AAAEA/v8CAP7/AgD//wAAAAD//wEAAAAAAAAA//8BAP7/BAD7/wUA/P8CAAAA//8BAP//AQAAAP//AgD9/wMA/v8BAAAA//8BAAAA//8CAP3/AwD+/wEA//8CAP3/AwD+/wAAAwD8/wIAAAD//wIA/////wIA/v8CAP7/AgD+/wEAAAAAAAAAAAAAAP//AgD+/wIA//8AAAAA//8CAP7/AgD+/wEA//8CAP3/AwD9/wMA/v8BAP7/AwD9/wMA/f8CAP//AQD+/wIA//8BAP//AQD+/wMA/v8BAAAA//8BAAAA//8CAP7/AQAAAP//AgD+/wIA/v8CAP//AAAAAAEA//8BAP//AAABAAAA//8BAP//AQD//wEA//8BAP//AQAAAP//AQD//wEAAAD//wIA/f8CAAAA//8BAAAA//8BAP//AAABAP//AQD//wAAAAAAAAEA/v8CAP//AQD//wAAAAABAP7/AwD9/wIAAAD+/wIA//8BAP//AgD9/wMA/f8DAP7/AgD+/wEAAAAAAAEA/v8CAP7/AgD//wAAAAAAAAAAAAAAAP//AgD/////AgD9/wQA/f8BAAAAAAAAAAEA/f8DAP////8DAP3/AQABAP7/AgD//wAAAQD+/wMA/f8CAP7/AQABAP7/AwD7/wYA+v8FAP3/AQABAP7/AgD+/wMA/f8CAP7/AwD+/wEA//8BAP//AQAAAP7/BQD5/wcA+v8FAPz/AwD+/wIA/v8BAAAA//8DAPv/BQD8/wMA/////wEAAAAAAAAAAAD//wIA/f8DAP7/AQAAAP//AQAAAP//AgD+/wIA/v8BAAEA/f8EAPz/AwD+/wEA//8CAP7/AQD//wEA//8CAP7/AQAAAP//AgD+/wEAAAAAAAAAAAAAAAAAAAD//wIA/f8EAPz/AwD+/wEA//8CAP7/AgD+/wEAAQD+/wEAAQD+/wIA/////wIA//8AAAAAAAAAAAAAAAD//wEAAAAAAP//AgD9/wMA/v8BAP//AQAAAP//AQD//wEA//8BAP//AQD//wEA//8BAP//AQAAAP7/AwD9/wMA/v8BAP7/AwD9/wMA/v8BAP//AAABAP//AQD//wAAAAABAP//AAAAAAAAAQD//wEA/v8CAAAA/v8EAPv/BAD9/wIAAAD+/wMA/P8DAP//AAAAAP//AQD//wIA/f8DAP3/AwD9/wMA/v8BAAAA//8BAAAA//8CAP3/AwD9/wQA+/8FAPv/BQD8/wMA/v8BAAAA//8BAP//AgD+/wEAAAD//wIA/v8BAAEA/f8DAP3/AgAAAP//AQD//wAAAQD//wEA//8BAP//AQD//wEA/v8DAP3/AgAAAP7/AwD9/wIAAAD//wEAAAD//wIA/f8DAP7/AgD9/wQA+/8FAPz/AgAAAP//AgD9/wIA//8BAP//AQD//wEA//8BAP//AQD//wIA/f8DAP3/AgD//wAAAQD+/wIA/v8BAAEA/v8CAP7/AgD+/wMA/P8DAP//AAABAP7/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA/v8CAP3/BAD8/wMA/v8BAAAAAAD//wEAAAAAAAAAAAD//wEAAAAAAAAA//8BAP//AgD+/wEA//8CAP3/AwD9/wMA/f8EAPv/BAD+/wAAAQD//wEA//8BAP//AAABAP//AQD//wEAAAD//wEA//8BAP//AgD9/wMA/v8AAAIA/f8DAP7/AAACAP3/AwD+/wEA//8BAP//AQAAAP//AQAAAP7/AwD9/wMA/v8AAAEA//8BAP//AAAAAAEA//8AAAEA/v8CAP//AAAAAAEA/v8DAPz/BAD9/wEAAQD+/wEAAQD9/wQA/P8DAP7/AQAAAAAAAAAAAAAAAAAAAAAAAQD+/wIA/////wIA/v8BAAAA//8BAP//AQD//wEA//8BAAAA/v8EAPz/AwD///7/BAD8/wMA/////wIA/v8CAP////8CAP7/AgD+/wIA/v8CAP////8CAP7/AwD9/wIA/v8CAP//AAABAP7/AwD9/wEAAQD+/wMA/f8CAP//AAAAAAEA/v8DAPz/BAD9/wIA/v8CAP7/AgD//wAAAAD//wIA/v8CAP7/AQAAAAAA//8CAP7/AgD+/wIA/v8CAP7/AwD8/wUA+v8GAPv/AwD//wAAAAAAAAAA//8DAPv/BQD9/wAAAgD9/wMA/v8BAP//AQAAAP//AgD9/wMA/v8BAAAA//8BAAAAAAAAAP//AQAAAAAAAAD//wEA//8CAP3/AwD+/wAAAgD+/wEAAAD//wIA/v8CAP7/AgD/////AwD8/wUA/P8CAP//AQD//wIA/f8DAP3/AwD+/wAAAQD+/wMA/f8DAP3/AgD//wAAAQD//wEA//8BAP7/AwD+/wEA//8AAAEA//8CAPz/BAD9/wIA//8AAAEA/v8DAPz/BAD9/wIA//8AAAEA/v8CAP7/AgD//wEA/f8EAPz/BAD+////AgD//wAAAQD//wAAAQD//wEA//8BAP7/AwD+/wEA"}}catch(o){return void addTest("audiopreload",!1)}r.setAttribute("preload","auto"),r.style.cssText="display:none",docElement.appendChild(r),setTimeout(function(){r.addEventListener("loadeddata",A,!1),e=setTimeout(A,t)},0)}),Modernizr.addTest("canvasblending",function(){if(Modernizr.canvas===!1)return!1;var A=createElement("canvas").getContext("2d");try{A.globalCompositeOperation="screen"}catch(e){}return"screen"===A.globalCompositeOperation});var canvas=createElement("canvas");Modernizr.addTest("todataurljpeg",function(){return!!Modernizr.canvas&&0===canvas.toDataURL("image/jpeg").indexOf("data:image/jpeg")}),Modernizr.addTest("todataurlpng",function(){return!!Modernizr.canvas&&0===canvas.toDataURL("image/png").indexOf("data:image/png")}),Modernizr.addTest("todataurlwebp",function(){var A=!1;try{A=!!Modernizr.canvas&&0===canvas.toDataURL("image/webp").indexOf("data:image/webp")}catch(e){}return A}),Modernizr.addTest("canvaswinding",function(){if(Modernizr.canvas===!1)return!1;var A=createElement("canvas").getContext("2d");return A.rect(0,0,10,10),A.rect(2,2,6,6),A.isPointInPath(5,5,"evenodd")===!1}),Modernizr.addTest("bgpositionshorthand",function(){var A=createElement("a"),e=A.style,t="right 10px bottom 10px";return e.cssText="background-position: "+t+";",e.backgroundPosition===t}),Modernizr.addTest("csscalc",function(){var A="width:",e="calc(10px);",t=createElement("a");return t.style.cssText=A+prefixes.join(e+A),!!t.style.length}),Modernizr.addTest("cubicbezierrange",function(){var A=createElement("a");return A.style.cssText=prefixes.join("transition-timing-function:cubic-bezier(1,0,0,1.1); "),!!A.style.length}),Modernizr.addTest("cssgradients",function(){for(var A,e="background-image:",t="gradient(linear,left top,right bottom,from(#9f9),to(white));",r="",n=0,o=prefixes.length-1;o>n;n++)A=0===n?"to ":"",r+=e+prefixes[n]+"linear-gradient("+A+"left top, #9f9, white);";Modernizr._config.usePrefixes&&(r+=e+"-webkit-"+t);var i=createElement("a"),d=i.style;return d.cssText=r,(""+d.backgroundImage).indexOf("gradient")>-1}),Modernizr.addTest("multiplebgs",function(){var A=createElement("a").style;return A.cssText="background:url(https://),url(https://),red url(https://)",/(url\s*\(.*?){3}/.test(A.background)}),Modernizr.addTest("opacity",function(){var A=createElement("a").style;return A.cssText=prefixes.join("opacity:.55;"),/^0.55$/.test(A.opacity)}),Modernizr.addTest("csspointerevents",function(){var A=createElement("a").style;return A.cssText="pointer-events:auto","auto"===A.pointerEvents}),Modernizr.addTest("csspositionsticky",function(){var A="position:",e="sticky",t=createElement("a"),r=t.style;return r.cssText=A+prefixes.join(e+";"+A).slice(0,-A.length),-1!==r.position.indexOf(e)}),Modernizr.addTest("cssremunit",function(){var A=createElement("a").style;try{A.fontSize="3rem"}catch(e){}return/rem/.test(A.fontSize)}),Modernizr.addTest("rgba",function(){var A=createElement("a").style;return A.cssText="background-color:rgba(150,255,150,.5)",(""+A.backgroundColor).indexOf("rgba")>-1}),Modernizr.addTest("preserve3d",function(){var A,e,t=window.CSS,r=!1;return t&&t.supports&&t.supports("(transform-style: preserve-3d)")?!0:(A=createElement("a"),e=createElement("a"),A.style.cssText="display: block; transform-style: preserve-3d; transform-origin: right; transform: rotateY(40deg);",e.style.cssText="display: block; width: 9px; height: 1px; background: #000; transform-origin: right; transform: rotateY(40deg);",A.appendChild(e),docElement.appendChild(A),r=e.getBoundingClientRect(),docElement.removeChild(A),r=r.width&&r.width<4)}),Modernizr.addTest("dataset",function(){var A=createElement("div");return A.setAttribute("data-a-b","c"),!(!A.dataset||"c"!==A.dataset.aB)}),Modernizr.addTest("hidden","hidden"in createElement("a")),Modernizr.addTest("outputelem","value"in createElement("output")),Modernizr.addTest("progressbar",createElement("progress").max!==undefined),Modernizr.addTest("meter",createElement("meter").max!==undefined),Modernizr.addTest("ruby",function(){function A(A,e){var t;return window.getComputedStyle?t=document.defaultView.getComputedStyle(A,null).getPropertyValue(e):A.currentStyle&&(t=A.currentStyle[e]),t}function e(){docElement.removeChild(t),t=null,r=null,n=null}var t=createElement("ruby"),r=createElement("rt"),n=createElement("rp"),o="display",i="fontSize";return t.appendChild(n),t.appendChild(r),docElement.appendChild(t),"none"==A(n,o)||"ruby"==A(t,o)&&"ruby-text"==A(r,o)||"6pt"==A(n,i)&&"6pt"==A(r,i)?(e(),!0):(e(),!1)}),Modernizr.addTest("template","content"in createElement("template")),Modernizr.addTest("time","valueAsDate"in createElement("time")),Modernizr.addTest("texttrackapi","function"==typeof createElement("video").addTextTrack),Modernizr.addTest("track","kind"in createElement("track")),Modernizr.addTest("unknownelements",function(){var A=createElement("a");return A.innerHTML="",1===A.childNodes.length}),Modernizr.addTest("capture","capture"in createElement("input")),Modernizr.addTest("fileinput",function(){if(navigator.userAgent.match(/(Android (1.0|1.1|1.5|1.6|2.0|2.1))|(Windows Phone (OS 7|8.0))|(XBLWP)|(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|(Kindle\/(1.0|2.0|2.5|3.0))/))return!1;var A=createElement("input");return A.type="file",!A.disabled}),Modernizr.addTest("fileinputdirectory",function(){var A=createElement("input"),e="directory";if(A.type="file",e in A)return!0;for(var t=0,r=domPrefixes.length;r>t;t++)if(domPrefixes[t]+e in A)return!0;return!1}),Modernizr.addTest("formattribute",function(){var A,e=createElement("form"),t=createElement("input"),r=createElement("div"),n="formtest"+(new Date).getTime(),o=!1;e.id=n;try{t.setAttribute("form",n)}catch(i){document.createAttribute&&(A=document.createAttribute("form"),A.nodeValue=n,t.setAttributeNode(A))}return r.appendChild(e),r.appendChild(t),docElement.appendChild(r),o=e.elements&&1===e.elements.length&&t.form==e,r.parentNode.removeChild(r),o}),Modernizr.addTest("placeholder","placeholder"in createElement("input")&&"placeholder"in createElement("textarea")),Modernizr.addTest("sandbox","sandbox"in createElement("iframe")),Modernizr.addTest("seamless","seamless"in createElement("iframe")),Modernizr.addTest("srcdoc","srcdoc"in createElement("iframe")),Modernizr.addAsyncTest(function(){if(!Modernizr.canvas)return!1;var A=new Image,e=createElement("canvas"),t=e.getContext("2d");A.onload=function(){addTest("apng",function(){return"undefined"==typeof e.getContext?!1:(t.drawImage(A,0,0),0===t.getImageData(0,0,1,1).data[3])})},A.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACGFjVEwAAAABAAAAAcMq2TYAAAANSURBVAiZY2BgYPgPAAEEAQB9ssjfAAAAGmZjVEwAAAAAAAAAAQAAAAEAAAAAAAAAAAD6A+gBAbNU+2sAAAARZmRBVAAAAAEImWNgYGBgAAAABQAB6MzFdgAAAABJRU5ErkJggg=="}),Modernizr.addAsyncTest(function(){var A,e,t,r=createElement("img"),n="sizes"in r;!n&&"srcset"in r?(e="data:image/gif;base64,R0lGODlhAgABAPAAAP///wAAACH5BAAAAAAALAAAAAACAAEAAAICBAoAOw==",A="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",t=function(){addTest("sizes",2==r.width)},r.onload=t,r.onerror=t,r.setAttribute("sizes","9px"),r.srcset=A+" 1w,"+e+" 8w",r.src=A):addTest("sizes",n)}),Modernizr.addTest("srcset","srcset"in createElement("img")),Modernizr.addTest("inputformaction",!!("formAction"in createElement("input")),{aliases:["input-formaction"]}),Modernizr.addTest("inputformenctype",!!("formEnctype"in createElement("input")),{aliases:["input-formenctype"]}),Modernizr.addTest("inputformmethod",!!("formMethod"in createElement("input"))),Modernizr.addTest("inputformtarget",!!("formtarget"in createElement("input")),{aliases:["input-formtarget"]}),Modernizr.addTest("scriptasync","async"in createElement("script")),Modernizr.addTest("scriptdefer","defer"in createElement("script")),Modernizr.addTest("stylescoped","scoped"in createElement("style")),Modernizr.addTest("inlinesvg",function(){var A=createElement("div");return A.innerHTML="","http://www.w3.org/2000/svg"==("undefined"!=typeof SVGRect&&A.firstChild&&A.firstChild.namespaceURI)}),Modernizr.addTest("textareamaxlength",!!("maxLength"in createElement("textarea"))),Modernizr.addAsyncTest(function(){function A(i){n++,clearTimeout(e);var d=i&&"playing"===i.type||0!==o.currentTime;return!d&&r>n?void(e=setTimeout(A,t)):(o.removeEventListener("playing",A,!1),addTest("videoautoplay",d),void(o.parentNode&&o.parentNode.removeChild(o)))}var e,t=200,r=5,n=0,o=createElement("video"),i=o.style;if(!(Modernizr.video&&"autoplay"in o))return void addTest("videoautoplay",!1);i.position="absolute",i.height=0,i.width=0;try{if(Modernizr.video.ogg)o.src="data:video/ogg;base64,T2dnUwACAAAAAAAAAABmnCATAAAAAHDEixYBKoB0aGVvcmEDAgEAAQABAAAQAAAQAAAAAAAFAAAAAQAAAAAAAAAAAGIAYE9nZ1MAAAAAAAAAAAAAZpwgEwEAAAACrA7TDlj///////////////+QgXRoZW9yYSsAAABYaXBoLk9yZyBsaWJ0aGVvcmEgMS4xIDIwMDkwODIyIChUaHVzbmVsZGEpAQAAABoAAABFTkNPREVSPWZmbXBlZzJ0aGVvcmEtMC4yOYJ0aGVvcmG+zSj3uc1rGLWpSUoQc5zmMYxSlKQhCDGMYhCEIQhAAAAAAAAAAAAAEW2uU2eSyPxWEvx4OVts5ir1aKtUKBMpJFoQ/nk5m41mUwl4slUpk4kkghkIfDwdjgajQYC8VioUCQRiIQh8PBwMhgLBQIg4FRba5TZ5LI/FYS/Hg5W2zmKvVoq1QoEykkWhD+eTmbjWZTCXiyVSmTiSSCGQh8PB2OBqNBgLxWKhQJBGIhCHw8HAyGAsFAiDgUCw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDAwPEhQUFQ0NDhESFRUUDg4PEhQVFRUOEBETFBUVFRARFBUVFRUVEhMUFRUVFRUUFRUVFRUVFRUVFRUVFRUVEAwLEBQZGxwNDQ4SFRwcGw4NEBQZHBwcDhATFhsdHRwRExkcHB4eHRQYGxwdHh4dGxwdHR4eHh4dHR0dHh4eHRALChAYKDM9DAwOExo6PDcODRAYKDlFOA4RFh0zV1A+EhYlOkRtZ00YIzdAUWhxXDFATldneXhlSFxfYnBkZ2MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTEhIVGRoaGhoSFBYaGhoaGhUWGRoaGhoaGRoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhESFh8kJCQkEhQYIiQkJCQWGCEkJCQkJB8iJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQREhgvY2NjYxIVGkJjY2NjGBo4Y2NjY2MvQmNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRISEhUXGBkbEhIVFxgZGxwSFRcYGRscHRUXGBkbHB0dFxgZGxwdHR0YGRscHR0dHhkbHB0dHR4eGxwdHR0eHh4REREUFxocIBERFBcaHCAiERQXGhwgIiUUFxocICIlJRcaHCAiJSUlGhwgIiUlJSkcICIlJSUpKiAiJSUlKSoqEBAQFBgcICgQEBQYHCAoMBAUGBwgKDBAFBgcICgwQEAYHCAoMEBAQBwgKDBAQEBgICgwQEBAYIAoMEBAQGCAgAfF5cdH1e3Ow/L66wGmYnfIUbwdUTe3LMRbqON8B+5RJEvcGxkvrVUjTMrsXYhAnIwe0dTJfOYbWrDYyqUrz7dw/JO4hpmV2LsQQvkUeGq1BsZLx+cu5iV0e0eScJ91VIQYrmqfdVSK7GgjOU0oPaPOu5IcDK1mNvnD+K8LwS87f8Jx2mHtHnUkTGAurWZlNQa74ZLSFH9oF6FPGxzLsjQO5Qe0edcpttd7BXBSqMCL4k/4tFrHIPuEQ7m1/uIWkbDMWVoDdOSuRQ9286kvVUlQjzOE6VrNguN4oRXYGkgcnih7t13/9kxvLYKQezwLTrO44sVmMPgMqORo1E0sm1/9SludkcWHwfJwTSybR4LeAz6ugWVgRaY8mV/9SluQmtHrzsBtRF/wPY+X0JuYTs+ltgrXAmlk10xQHmTu9VSIAk1+vcvU4ml2oNzrNhEtQ3CysNP8UeR35wqpKUBdGdZMSjX4WVi8nJpdpHnbhzEIdx7mwf6W1FKAiucMXrWUWVjyRf23chNtR9mIzDoT/6ZLYailAjhFlZuvPtSeZ+2oREubDoWmT3TguY+JHPdRVSLKxfKH3vgNqJ/9emeEYikGXDFNzaLjvTeGAL61mogOoeG3y6oU4rW55ydoj0lUTSR/mmRhPmF86uwIfzp3FtiufQCmppaHDlGE0r2iTzXIw3zBq5hvaTldjG4CPb9wdxAme0SyedVKczJ9AtYbgPOzYKJvZZImsN7ecrxWZg5dR6ZLj/j4qpWsIA+vYwE+Tca9ounMIsrXMB4Stiib2SPQtZv+FVIpfEbzv8ncZoLBXc3YBqTG1HsskTTotZOYTG+oVUjLk6zhP8bg4RhMUNtfZdO7FdpBuXzhJ5Fh8IKlJG7wtD9ik8rWOJxy6iQ3NwzBpQ219mlyv+FLicYs2iJGSE0u2txzed++D61ZWCiHD/cZdQVCqkO2gJpdpNaObhnDfAPrT89RxdWFZ5hO3MseBSIlANppdZNIV/Rwe5eLTDvkfWKzFnH+QJ7m9QWV1KdwnuIwTNtZdJMoXBf74OhRnh2t+OTGL+AVUnIkyYY+QG7g9itHXyF3OIygG2s2kud679ZWKqSFa9n3IHD6MeLv1lZ0XyduRhiDRtrNnKoyiFVLcBm0ba5Yy3fQkDh4XsFE34isVpOzpa9nR8iCpS4HoxG2rJpnRhf3YboVa1PcRouh5LIJv/uQcPNd095ickTaiGBnWLKVWRc0OnYTSyex/n2FofEPnDG8y3PztHrzOLK1xo6RAml2k9owKajOC0Wr4D5x+3nA0UEhK2m198wuBHF3zlWWVKWLN1CHzLClUfuoYBcx4b1llpeBKmbayaR58njtE9onD66lUcsg0Spm2snsb+8HaJRn4dYcLbCuBuYwziB8/5U1C1DOOz2gZjSZtrLJk6vrLF3hwY4Io9xuT/ruUFRSBkNtUzTOWhjh26irLEPx4jPZL3Fo3QrReoGTTM21xYTT9oFdhTUIvjqTkfkvt0bzgVUjq/hOYY8j60IaO/0AzRBtqkTS6R5ellZd5uKdzzhb8BFlDdAcrwkE0rbXTOPB+7Y0FlZO96qFL4Ykg21StJs8qIW7h16H5hGiv8V2Cflau7QVDepTAHa6Lgt6feiEvJDM21StJsmOH/hynURrKxvUpQ8BH0JF7BiyG2qZpnL/7AOU66gt+reLEXY8pVOCQvSsBtqZTNM8bk9ohRcwD18o/WVkbvrceVKRb9I59IEKysjBeTMmmbA21xu/6iHadLRxuIzkLpi8wZYmmbbWi32RVAUjruxWlJ//iFxE38FI9hNKOoCdhwf5fDe4xZ81lgREhK2m1j78vW1CqkuMu/AjBNK210kzRUX/B+69cMMUG5bYrIeZxVSEZISmkzbXOi9yxwIfPgdsov7R71xuJ7rFcACjG/9PzApqFq7wEgzNJm2suWESPuwrQvejj7cbnQxMkxpm21lUYJL0fKmogPPqywn7e3FvB/FCNxPJ85iVUkCE9/tLKx31G4CgNtWTTPFhMvlu8G4/TrgaZttTChljfNJGgOT2X6EqpETy2tYd9cCBI4lIXJ1/3uVUllZEJz4baqGF64yxaZ+zPLYwde8Uqn1oKANtUrSaTOPHkhvuQP3bBlEJ/LFe4pqQOHUI8T8q7AXx3fLVBgSCVpMba55YxN3rv8U1Dv51bAPSOLlZWebkL8vSMGI21lJmmeVxPRwFlZF1CpqCN8uLwymaZyjbXHCRytogPN3o/n74CNykfT+qqRv5AQlHcRxYrC5KvGmbbUwmZY/29BvF6C1/93x4WVglXDLFpmbapmF89HKTogRwqqSlGbu+oiAkcWFbklC6Zhf+NtTLFpn8oWz+HsNRVSgIxZWON+yVyJlE5tq/+GWLTMutYX9ekTySEQPLVNQQ3OfycwJBM0zNtZcse7CvcKI0V/zh16Dr9OSA21MpmmcrHC+6pTAPHPwoit3LHHqs7jhFNRD6W8+EBGoSEoaZttTCZljfduH/fFisn+dRBGAZYtMzbVMwvul/T/crK1NQh8gN0SRRa9cOux6clC0/mDLFpmbarmF8/e6CopeOLCNW6S/IUUg3jJIYiAcDoMcGeRbOvuTPjXR/tyo79LK3kqqkbxkkMRAOB0GODPItnX3Jnxro/25Ud+llbyVVSN4ySGIgHA6DHBnkWzr7kz410f7cqO/Syt5KqpFVJwn6gBEvBM0zNtZcpGOEPiysW8vvRd2R0f7gtjhqUvXL+gWVwHm4XJDBiMpmmZtrLfPwd/IugP5+fKVSysH1EXreFAcEhelGmbbUmZY4Xdo1vQWVnK19P4RuEnbf0gQnR+lDCZlivNM22t1ESmopPIgfT0duOfQrsjgG4tPxli0zJmF5trdL1JDUIUT1ZXSqQDeR4B8mX3TrRro/2McGeUvLtwo6jIEKMkCUXWsLyZROd9P/rFYNtXPBli0z398iVUlVKAjFlY437JXImUTm2r/4ZYtMy61hf16RPJIU9nZ1MABAwAAAAAAAAAZpwgEwIAAABhp658BScAAAAAAADnUFBQXIDGXLhwtttNHDhw5OcpQRMETBEwRPduylKVB0HRdF0A";else{if(!Modernizr.video.h264)return void addTest("videoautoplay",!1);o.src="data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAs1tZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0OCByMjYwMSBhMGNkN2QzIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNSAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTEgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEwIHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAD2WIhAA3//728P4FNjuZQQAAAu5tb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAAZAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACGHRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAZAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAgAAAAIAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAGQAAAAAAAEAAAAAAZBtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAACgAAAAEAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAE7bWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAAA+3N0YmwAAACXc3RzZAAAAAAAAAABAAAAh2F2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAgACAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAxYXZjQwFkAAr/4QAYZ2QACqzZX4iIhAAAAwAEAAADAFA8SJZYAQAGaOvjyyLAAAAAGHN0dHMAAAAAAAAAAQAAAAEAAAQAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAABRzdHN6AAAAAAAAAsUAAAABAAAAFHN0Y28AAAAAAAAAAQAAADAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjU2LjQwLjEwMQ=="}}catch(d){return void addTest("videoautoplay",!1)}o.setAttribute("autoplay",""),o.style.cssText="display:none",docElement.appendChild(o),setTimeout(function(){o.addEventListener("playing",A,!1),e=setTimeout(A,t)},0)}),Modernizr.addTest("videoloop","loop"in createElement("video")),Modernizr.addTest("videopreload","preload"in createElement("video")),Modernizr.addAsyncTest(function(){if(Modernizr.webglextensions=!1,Modernizr.webgl){var A,e,t;try{A=createElement("canvas"),e=A.getContext("webgl")||A.getContext("experimental-webgl"),t=e.getSupportedExtensions()}catch(r){return}e!==undefined&&(Modernizr.webglextensions=new Boolean(!0));for(var n=-1,o=t.length;++ne.offsetWidth}),A}),Modernizr.addTest("touchevents",function(){var A;if("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch)A=!0;else{var e=["@media (",prefixes.join("touch-enabled),("),"heartz",")","{#modernizr{top:9px;position:absolute}}"].join("");testStyles(e,function(e){A=9===e.offsetTop})}return A}),Modernizr.addTest("unicoderange",function(){return Modernizr.testStyles('@font-face{font-family:"unicodeRange";src:local("Arial");unicode-range:U+0020,U+002E}#modernizr span{font-size:20px;display:inline-block;font-family:"unicodeRange",monospace}#modernizr .mono{font-family:monospace}',function(A){for(var e=[".",".","m","m"],t=0;t=9;return e||t}();blacklist?Modernizr.addTest("fontface",!1):testStyles('@font-face {font-family:"font";src:url("https://")}',function(A,e){var t=document.getElementById("smodernizr"),r=t.sheet||t.styleSheet,n=r?r.cssRules&&r.cssRules[0]?r.cssRules[0].cssText:r.cssText||"":"",o=/src/i.test(n)&&0===n.indexOf(e.split(" ")[0]);Modernizr.addTest("fontface",o)}),testStyles('#modernizr{font:0/0 a}#modernizr:after{content:":)";visibility:hidden;font:7px/1 a}',function(A){Modernizr.addTest("generatedcontent",A.offsetHeight>=6)}),Modernizr.addTest("cssinvalid",function(){return testStyles("#modernizr input{height:0;border:0;padding:0;margin:0;width:10px} #modernizr input:invalid{width:50px}",function(A){var e=createElement("input");return e.required=!0,A.appendChild(e),e.clientWidth>10})}),testStyles("#modernizr div {width:100px} #modernizr :last-child{width:200px;display:block}",function(A){Modernizr.addTest("lastchild",A.lastChild.offsetWidth>A.firstChild.offsetWidth)},2),testStyles("#modernizr div {width:1px} #modernizr div:nth-child(2n) {width:2px;}",function(A){for(var e=A.getElementsByTagName("div"),t=!0,r=0;5>r;r++)t=t&&e[r].offsetWidth===r%2+1;Modernizr.addTest("nthchild",t)},5),testStyles("#modernizr{overflow: scroll; width: 40px; height: 40px; }#"+prefixes.join("scrollbar{width:10px} #modernizr::").split("#").slice(1).join("#")+"scrollbar{width:10px}",function(A){Modernizr.addTest("cssscrollbar","scrollWidth"in A&&30==A.scrollWidth)}),Modernizr.addTest("siblinggeneral",function(){return testStyles("#modernizr div {width:100px} #modernizr div ~ div {width:200px;display:block}",function(A){return 200==A.lastChild.offsetWidth},2)}),testStyles("#modernizr{position: absolute; top: -10em; visibility:hidden; font: normal 10px arial;}#subpixel{float: left; font-size: 33.3333%;}",function(A){var e=A.firstChild;e.innerHTML="This is a text written in Arial",Modernizr.addTest("subpixelfont",window.getComputedStyle?"44px"!==window.getComputedStyle(e,null).getPropertyValue("width"):!1)},1,["subpixel"]),Modernizr.addTest("cssvalid",function(){return testStyles("#modernizr input{height:0;border:0;padding:0;margin:0;width:10px} #modernizr input:valid{width:50px}",function(A){var e=createElement("input");return A.appendChild(e),e.clientWidth>10})}),Modernizr.addTest("details",function(){var A,e=createElement("details");return"open"in e?(testStyles("#modernizr details{display:block}",function(t){t.appendChild(e),e.innerHTML="ab",A=e.offsetHeight,e.open=!0,A=A!=e.offsetHeight}),A):!1}),Modernizr.addTest("oninput",function(){var A,e=createElement("input");if(e.setAttribute("oninput","return"),hasEvent("oninput",docElement)||"function"==typeof e.oninput)return!0;try{var t=document.createEvent("KeyboardEvent");A=!1;var r=function(e){A=!0,e.preventDefault(),e.stopPropagation()};t.initKeyEvent("keypress",!0,!0,window,!1,!1,!1,!1,0,"e".charCodeAt(0)),docElement.appendChild(e),e.addEventListener("input",r,!1),e.focus(),e.dispatchEvent(t),e.removeEventListener("input",r,!1),docElement.removeChild(e)}catch(n){A=!1}return A}),Modernizr.addTest("formvalidation",function(){var A=createElement("form");if(!("checkValidity"in A&&"addEventListener"in A))return!1; -if("reportValidity"in A)return!0;var e,t=!1;return Modernizr.formvalidationapi=!0,A.addEventListener("submit",function(A){(!window.opera||window.operamini)&&A.preventDefault(),A.stopPropagation()},!1),A.innerHTML='',testStyles("#modernizr form{position:absolute;top:-99999em}",function(r){r.appendChild(A),e=A.getElementsByTagName("input")[0],e.addEventListener("invalid",function(A){t=!0,A.preventDefault(),A.stopPropagation()},!1),Modernizr.formvalidationmessage=!!e.validationMessage,A.getElementsByTagName("button")[0].click()}),t});var inputElem=createElement("input"),inputattrs="autocomplete autofocus list placeholder max min multiple pattern required step".split(" "),attrs={};Modernizr.input=function(A){for(var e=0,t=A.length;t>e;e++)attrs[A[e]]=!!(A[e]in inputElem);return attrs.list&&(attrs.list=!(!createElement("datalist")||!window.HTMLDataListElement)),attrs}(inputattrs),Modernizr.addTest("datalistelem",Modernizr.input.list);var inputtypes="search tel url email datetime date month week time datetime-local number range color".split(" "),inputs={};Modernizr.inputtypes=function(A){for(var e,t,r,n=A.length,o="1)",i=0;n>i;i++)inputElem.setAttribute("type",e=A[i]),r="text"!==inputElem.type&&"style"in inputElem,r&&(inputElem.value=o,inputElem.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(e)&&inputElem.style.WebkitAppearance!==undefined?(docElement.appendChild(inputElem),t=document.defaultView,r=t.getComputedStyle&&"textfield"!==t.getComputedStyle(inputElem,null).WebkitAppearance&&0!==inputElem.offsetHeight,docElement.removeChild(inputElem)):/^(search|tel)$/.test(e)||(r=/^(url|email)$/.test(e)?inputElem.checkValidity&&inputElem.checkValidity()===!1:inputElem.value!=o)),inputs[A[i]]=!!r;return inputs}(inputtypes),Modernizr.addTest("localizednumber",function(){if(!Modernizr.inputtypes.number)return!1;if(!Modernizr.formvalidation)return!1;var A,e=createElement("div"),t=getBody(),r=function(){return docElement.insertBefore(t,docElement.firstElementChild||docElement.firstChild)}();e.innerHTML='';var n=e.childNodes[0];r.appendChild(e),n.focus();try{document.execCommand("SelectAll",!1),document.execCommand("InsertText",!1,"1,1")}catch(o){}return A="number"===n.type&&1.1===n.valueAsNumber&&n.checkValidity(),r.removeChild(e),t.fake&&r.parentNode.removeChild(r),A});var modElem={elem:createElement("modernizr")};Modernizr._q.push(function(){delete modElem.elem}),Modernizr.addTest("csschunit",function(){var A,e=modElem.elem.style;try{e.fontSize="3ch",A=-1!==e.fontSize.indexOf("ch")}catch(t){A=!1}return A}),Modernizr.addTest("cssexunit",function(){var A,e=modElem.elem.style;try{e.fontSize="3ex",A=-1!==e.fontSize.indexOf("ex")}catch(t){A=!1}return A}),Modernizr.addTest("hsla",function(){var A=createElement("a").style;return A.cssText="background-color:hsla(120,40%,100%,.5)",contains(A.backgroundColor,"rgba")||contains(A.backgroundColor,"hsla")}),testStyles("#modernizr { height: 50vh; }",function(A){var e=parseInt(window.innerHeight/2,10),t=parseInt(computedStyle(A,null,"height"),10);Modernizr.addTest("cssvhunit",t==e)}),testStyles("#modernizr { width: 50vw; }",function(A){var e=parseInt(window.innerWidth/2,10),t=parseInt(computedStyle(A,null,"width"),10);Modernizr.addTest("cssvwunit",t==e)}),Modernizr.addTest("bdi",function(){var A=createElement("div"),e=createElement("bdi");e.innerHTML="إ",A.appendChild(e),docElement.appendChild(A);var t="rtl"===computedStyle(e,null,"direction");return docElement.removeChild(A),t}),testStyles("#modernizr1{width: 50vmax}#modernizr2{width:50px;height:50px;overflow:scroll}#modernizr3{position:fixed;top:0;left:0;bottom:0;right:0}",function(A){var e=A.childNodes[2],t=A.childNodes[1],r=A.childNodes[0],n=parseInt((t.offsetWidth-t.clientWidth)/2,10),o=r.clientWidth/100,i=r.clientHeight/100,d=parseInt(50*Math.max(o,i),10),a=parseInt(computedStyle(e,null,"width"),10);Modernizr.addTest("cssvmaxunit",roundedEquals(d,a)||roundedEquals(d,a-n))},3),testStyles("#modernizr1{width: 50vm;width:50vmin}#modernizr2{width:50px;height:50px;overflow:scroll}#modernizr3{position:fixed;top:0;left:0;bottom:0;right:0}",function(A){var e=A.childNodes[2],t=A.childNodes[1],r=A.childNodes[0],n=parseInt((t.offsetWidth-t.clientWidth)/2,10),o=r.clientWidth/100,i=r.clientHeight/100,d=parseInt(50*Math.min(o,i),10),a=parseInt(computedStyle(e,null,"width"),10);Modernizr.addTest("cssvminunit",roundedEquals(d,a)||roundedEquals(d,a-n))},3);var testXhrType=function(A){if("undefined"==typeof XMLHttpRequest)return!1;var e=new XMLHttpRequest;e.open("get","/",!0);try{e.responseType=A}catch(t){return!1}return"response"in e&&e.responseType==A};Modernizr.addTest("xhrresponsetypearraybuffer",testXhrType("arraybuffer")),Modernizr.addTest("xhrresponsetypedocument",testXhrType("document")),Modernizr.addTest("xhrresponsetypeblob",testXhrType("blob")),Modernizr.addTest("xhrresponsetypejson",testXhrType("json")),Modernizr.addTest("xhrresponsetypetext",testXhrType("text"));var toStringFn={}.toString;Modernizr.addTest("svgclippaths",function(){return!!document.createElementNS&&/SVGClipPath/.test(toStringFn.call(document.createElementNS("http://www.w3.org/2000/svg","clipPath")))}),Modernizr.addTest("svgforeignobject",function(){return!!document.createElementNS&&/SVGForeignObject/.test(toStringFn.call(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")))}),Modernizr.addTest("smil",function(){return!!document.createElementNS&&/SVGAnimate/.test(toStringFn.call(document.createElementNS("http://www.w3.org/2000/svg","animate")))});var mStyle={style:modElem.elem.style};Modernizr._q.unshift(function(){delete mStyle.style});var testProp=ModernizrProto.testProp=function(A,e,t){return testProps([A],undefined,e,t)};Modernizr.addTest("textshadow",testProp("textShadow","1px 1px")),ModernizrProto.testAllProps=testPropsAll;var prefixed=ModernizrProto.prefixed=function(A,e,t){return 0===A.indexOf("@")?atRule(A):(-1!=A.indexOf("-")&&(A=cssToDOM(A)),e?testPropsAll(A,e,t):testPropsAll(A,"pfx"))},prefixedCSS=ModernizrProto.prefixedCSS=function(A){var e=prefixed(A);return e&&domToCSS(e)};Modernizr.addTest("batteryapi",!!prefixed("battery",navigator),{aliases:["battery-api"]});var crypto=prefixed("crypto",window);Modernizr.addTest("crypto",!!prefixed("subtle",crypto)),Modernizr.addTest("dart",!!prefixed("startDart",navigator)),Modernizr.addTest("fullscreen",!(!prefixed("exitFullscreen",document,!1)&&!prefixed("cancelFullScreen",document,!1))),Modernizr.addTest("gamepads",!!prefixed("getGamepads",navigator)),Modernizr.addAsyncTest(function(){var A;try{A=prefixed("indexedDB",window)}catch(e){}if(A){var t="modernizr-"+Math.random(),r=A.open(t);r.onerror=function(){r.error&&"InvalidStateError"===r.error.name?addTest("indexeddb",!1):(addTest("indexeddb",!0),detectDeleteDatabase(A,t))},r.onsuccess=function(){addTest("indexeddb",!0),detectDeleteDatabase(A,t)}}else addTest("indexeddb",!1)}),Modernizr.addAsyncTest(function(){var A,e,t,r,n="detect-blob-support",o=!1;try{A=prefixed("indexedDB",window)}catch(i){}if(!Modernizr.indexeddb||!Modernizr.indexeddb.deletedatabase)return!1;try{A.deleteDatabase(n).onsuccess=function(){e=A.open(n,1),e.onupgradeneeded=function(){e.result.createObjectStore("store")},e.onsuccess=function(){t=e.result;try{r=t.transaction("store","readwrite").objectStore("store").put(new Blob,"key"),r.onsuccess=function(){o=!0},r.onerror=function(){o=!1}}catch(i){o=!1}finally{addTest("indexeddbblob",o),t.close(),A.deleteDatabase(n)}}}}catch(i){addTest("indexeddbblob",!1)}}),Modernizr.addTest("intl",!!prefixed("Intl",window)),Modernizr.addTest("pagevisibility",!!prefixed("hidden",document,!1)),Modernizr.addTest("performance",!!prefixed("performance",window)),Modernizr.addTest("pointerlock",!!prefixed("exitPointerLock",document)),Modernizr.addTest("quotamanagement",function(){var A=prefixed("temporaryStorage",navigator),e=prefixed("persistentStorage",navigator);return!(!A||!e)}),Modernizr.addTest("requestanimationframe",!!prefixed("requestAnimationFrame",window),{aliases:["raf"]}),Modernizr.addTest("vibrate",!!prefixed("vibrate",navigator)),Modernizr.addTest("webintents",!!prefixed("startActivity",navigator)),Modernizr.addTest("lowbattery",function(){var A=.2,e=prefixed("battery",navigator);return!!(e&&!e.charging&&e.level<=A)});var crypto=prefixed("crypto",window),supportsGetRandomValues;if(crypto&&"getRandomValues"in crypto&&"Uint32Array"in window){var array=new Uint32Array(10),values=crypto.getRandomValues(array);supportsGetRandomValues=values&&is(values[0],"number")}Modernizr.addTest("getrandomvalues",!!supportsGetRandomValues),Modernizr.addTest("backgroundblendmode",prefixed("backgroundBlendMode","text")),Modernizr.addTest("objectfit",!!prefixed("objectFit"),{aliases:["object-fit"]}),Modernizr.addTest("regions",function(){if(isSVG)return!1;var A=prefixed("flowFrom"),e=prefixed("flowInto"),t=!1;if(!A||!e)return t;var r=createElement("iframe"),n=createElement("div"),o=createElement("div"),i=createElement("div"),d="modernizr_flow_for_regions_check";o.innerText="M",n.style.cssText="top: 150px; left: 150px; padding: 0px;",i.style.cssText="width: 50px; height: 50px; padding: 42px;",i.style[A]=d,n.appendChild(o),n.appendChild(i),docElement.appendChild(n);var a,s,l=o.getBoundingClientRect();return o.style[e]=d,a=o.getBoundingClientRect(),s=parseInt(a.left-l.left,10),docElement.removeChild(n),42==s?t=!0:(docElement.appendChild(r),l=r.getBoundingClientRect(),r.style[e]=d,a=r.getBoundingClientRect(),l.height>0&&l.height!==a.height&&0===a.height&&(t=!0)),o=i=n=r=undefined,t}),Modernizr.addTest("wrapflow",function(){var A=prefixed("wrapFlow");if(!A||isSVG)return!1;var e=A.replace(/([A-Z])/g,function(A,e){return"-"+e.toLowerCase()}).replace(/^ms-/,"-ms-"),t=createElement("div"),r=createElement("div"),n=createElement("span");r.style.cssText="position: absolute; left: 50px; width: 100px; height: 20px;"+e+":end;",n.innerText="X",t.appendChild(r),t.appendChild(n),docElement.appendChild(t);var o=n.offsetLeft;return docElement.removeChild(t),r=n=t=undefined,150==o}),Modernizr.addTest("filesystem",!!prefixed("requestFileSystem",window)),Modernizr.addTest("requestautocomplete",!!prefixed("requestAutocomplete",createElement("form"))),Modernizr.addTest("speechrecognition",!!prefixed("SpeechRecognition",window));var url=prefixed("URL",window,!1);url=url&&window[url],Modernizr.addTest("bloburls",url&&"revokeObjectURL"in url&&"createObjectURL"in url),Modernizr.addAsyncTest(function(){function A(){addTest("transferables",!1),e()}function e(){d&&URL.revokeObjectURL(d),a&&a.terminate(),n&&clearTimeout(n)}var t=!!(Modernizr.blobconstructor&&Modernizr.bloburls&&Modernizr.webworkers&&Modernizr.typedarrays);if(!t)return addTest("transferables",!1);try{var r,n,o='var hello = "world"',i=new Blob([o],{type:"text/javascript"}),d=URL.createObjectURL(i),a=new Worker(d);a.onerror=A,n=setTimeout(A,200),r=new ArrayBuffer(1),a.postMessage(r,[r]),addTest("transferables",0===r.byteLength),e()}catch(s){A()}}),Modernizr.addTest("getusermedia",!!prefixed("getUserMedia",navigator)),Modernizr.addTest("peerconnection",!!prefixed("RTCPeerConnection",window)),Modernizr.addTest("matchmedia",!!prefixed("matchMedia",window)),ModernizrProto.testAllProps=testAllProps,Modernizr.addTest("cssanimations",testAllProps("animationName","a",!0)),Modernizr.addTest("csspseudoanimations",function(){var A=!1;if(!Modernizr.cssanimations||!window.getComputedStyle)return A;var e=["@",Modernizr._prefixes.join("keyframes csspseudoanimations { from { font-size: 10px; } }@").replace(/\@$/,""),'#modernizr:before { content:" "; font-size:5px;',Modernizr._prefixes.join("animation:csspseudoanimations 1ms infinite;"),"}"].join("");return Modernizr.testStyles(e,function(e){A="10px"===window.getComputedStyle(e,":before").getPropertyValue("font-size")}),A}),Modernizr.addTest("appearance",testAllProps("appearance")),Modernizr.addTest("backdropfilter",testAllProps("backdropFilter")),Modernizr.addTest("backgroundcliptext",function(){return testAllProps("backgroundClip","text")}),Modernizr.addTest("bgpositionxy",function(){return testAllProps("backgroundPositionX","3px",!0)&&testAllProps("backgroundPositionY","5px",!0)}),Modernizr.addTest("bgrepeatround",testAllProps("backgroundRepeat","round")),Modernizr.addTest("bgrepeatspace",testAllProps("backgroundRepeat","space")),Modernizr.addTest("backgroundsize",testAllProps("backgroundSize","100%",!0)),Modernizr.addTest("bgsizecover",testAllProps("backgroundSize","cover")),Modernizr.addTest("borderimage",testAllProps("borderImage","url() 1",!0)),Modernizr.addTest("borderradius",testAllProps("borderRadius","0px",!0)),Modernizr.addTest("boxshadow",testAllProps("boxShadow","1px 1px",!0)),Modernizr.addTest("boxsizing",testAllProps("boxSizing","border-box",!0)&&(document.documentMode===undefined||document.documentMode>7)),function(){Modernizr.addTest("csscolumns",function(){var A=!1,e=testAllProps("columnCount");try{A=!!e,A&&(A=new Boolean(A))}catch(t){}return A});for(var A,e,t=["Width","Span","Fill","Gap","Rule","RuleColor","RuleStyle","RuleWidth","BreakBefore","BreakAfter","BreakInside"],r=0;r9)}),Modernizr.addTest("flexbox",testAllProps("flexBasis","1px",!0)),Modernizr.addTest("flexboxlegacy",testAllProps("boxDirection","reverse",!0)),Modernizr.addTest("flexboxtweener",testAllProps("flexAlign","end",!0)),Modernizr.addTest("flexwrap",testAllProps("flexWrap","wrap",!0)),Modernizr.addAsyncTest(function(){function A(){function t(){try{var A=createElement("div"),e=createElement("span"),t=A.style,r=0,n=0,o=!1,i=document.body.firstElementChild||document.body.firstChild;return A.appendChild(e),e.innerHTML="Bacon ipsum dolor sit amet jerky velit in culpa hamburger et. Laborum dolor proident, enim dolore duis commodo et strip steak. Salami anim et, veniam consectetur dolore qui tenderloin jowl velit sirloin. Et ad culpa, fatback cillum jowl ball tip ham hock nulla short ribs pariatur aute. Pig pancetta ham bresaola, ut boudin nostrud commodo flank esse cow tongue culpa. Pork belly bresaola enim pig, ea consectetur nisi. Fugiat officia turkey, ea cow jowl pariatur ullamco proident do laborum velit sausage. Magna biltong sint tri-tip commodo sed bacon, esse proident aliquip. Ullamco ham sint fugiat, velit in enim sed mollit nulla cow ut adipisicing nostrud consectetur. Proident dolore beef ribs, laborum nostrud meatball ea laboris rump cupidatat labore culpa. Shankle minim beef, velit sint cupidatat fugiat tenderloin pig et ball tip. Ut cow fatback salami, bacon ball tip et in shank strip steak bresaola. In ut pork belly sed mollit tri-tip magna culpa veniam, short ribs qui in andouille ham consequat. Dolore bacon t-bone, velit short ribs enim strip steak nulla. Voluptate labore ut, biltong swine irure jerky. Cupidatat excepteur aliquip salami dolore. Ball tip strip steak in pork dolor. Ad in esse biltong. Dolore tenderloin exercitation ad pork loin t-bone, dolore in chicken ball tip qui pig. Ut culpa tongue, sint ribeye dolore ex shank voluptate hamburger. Jowl et tempor, boudin pork chop labore ham hock drumstick consectetur tri-tip elit swine meatball chicken ground round. Proident shankle mollit dolore. Shoulder ut duis t-bone quis reprehenderit. Meatloaf dolore minim strip steak, laboris ea aute bacon beef ribs elit shank in veniam drumstick qui. Ex laboris meatball cow tongue pork belly. Ea ball tip reprehenderit pig, sed fatback boudin dolore flank aliquip laboris eu quis. Beef ribs duis beef, cow corned beef adipisicing commodo nisi deserunt exercitation. Cillum dolor t-bone spare ribs, ham hock est sirloin. Brisket irure meatloaf in, boudin pork belly sirloin ball tip. Sirloin sint irure nisi nostrud aliqua. Nostrud nulla aute, enim officia culpa ham hock. Aliqua reprehenderit dolore sunt nostrud sausage, ea boudin pork loin ut t-bone ham tempor. Tri-tip et pancetta drumstick laborum. Ham hock magna do nostrud in proident. Ex ground round fatback, venison non ribeye in.",document.body.insertBefore(A,i),t.cssText="position:absolute;top:0;left:0;width:5em;text-align:justify;text-justification:newspaper;",r=e.offsetHeight,n=e.offsetWidth,t.cssText="position:absolute;top:0;left:0;width:5em;text-align:justify;text-justification:newspaper;"+prefixes.join("hyphens:auto; "),o=e.offsetHeight!=r||e.offsetWidth!=n,document.body.removeChild(A),A.removeChild(e),o}catch(d){return!1}}function r(A,e){try{var t=createElement("div"),r=createElement("span"),n=t.style,o=0,i=!1,d=!1,a=!1,s=document.body.firstElementChild||document.body.firstChild;return n.cssText="position:absolute;top:0;left:0;overflow:visible;width:1.25em;",t.appendChild(r),document.body.insertBefore(t,s),r.innerHTML="mm",o=r.offsetHeight,r.innerHTML="m"+A+"m",d=r.offsetHeight>o,e?(r.innerHTML="m
    m",o=r.offsetWidth,r.innerHTML="m"+A+"m",a=r.offsetWidth>o):a=!0,d===!0&&a===!0&&(i=!0),document.body.removeChild(t),t.removeChild(r),i}catch(l){return!1}}function n(A){try{var e,t=createElement("input"),r=createElement("div"),n="lebowski",o=!1,i=document.body.firstElementChild||document.body.firstChild;r.innerHTML=n+A+n,document.body.insertBefore(r,i),document.body.insertBefore(t,r),t.setSelectionRange?(t.focus(),t.setSelectionRange(0,0)):t.createTextRange&&(e=t.createTextRange(),e.collapse(!0),e.moveEnd("character",0),e.moveStart("character",0),e.select());try{window.find?o=window.find(n+n):(e=window.self.document.body.createTextRange(),o=e.findText(n+n))}catch(d){o=!1}return document.body.removeChild(r),document.body.removeChild(t),o}catch(d){return!1}}return document.body||document.getElementsByTagName("body")[0]?(addTest("csshyphens",function(){if(!testAllProps("hyphens","auto",!0))return!1;try{return t()}catch(A){return!1}}),addTest("softhyphens",function(){try{return r("­",!0)&&r("​",!1)}catch(A){return!1}}),void addTest("softhyphensfind",function(){try{return n("­")&&n("​")}catch(A){return!1}})):void setTimeout(A,e)}var e=300;setTimeout(A,e)}),Modernizr.addTest("cssmask",testAllProps("maskRepeat","repeat-x",!0)),Modernizr.addTest("overflowscrolling",testAllProps("overflowScrolling","touch",!0)),Modernizr.addTest("cssreflections",testAllProps("boxReflect","above",!0)),Modernizr.addTest("cssresize",testAllProps("resize","both",!0)),Modernizr.addTest("shapes",testAllProps("shapeOutside","content-box",!0)),Modernizr.addTest("textalignlast",testAllProps("textAlignLast")),Modernizr.addTest("csstransforms",function(){return-1===navigator.userAgent.indexOf("Android 2.")&&testAllProps("transform","scale(1)",!0)}),Modernizr.addTest("csstransforms3d",function(){var A=!!testAllProps("perspective","1px",!0),e=Modernizr._config.usePrefixes;if(A&&(!e||"webkitPerspective"in docElement.style)){var t,r="#modernizr{width:0;height:0}";Modernizr.supports?t="@supports (perspective: 1px)":(t="@media (transform-3d)",e&&(t+=",(-webkit-transform-3d)")),t+="{#modernizr{width:7px;height:18px;margin:0;padding:0;border:0}}",testStyles(r+t,function(e){A=7===e.offsetWidth&&18===e.offsetHeight})}return A}),Modernizr.addTest("csstransitions",testAllProps("transition","all",!0)),Modernizr.addTest("csspseudotransitions",function(){var A=!1;if(!Modernizr.csstransitions||!window.getComputedStyle)return A;var e='#modernizr:before { content:" "; font-size:5px;'+Modernizr._prefixes.join("transition:0s 100s;")+"}#modernizr.trigger:before { font-size:10px; }";return Modernizr.testStyles(e,function(e){window.getComputedStyle(e,":before").getPropertyValue("font-size"),e.className+="trigger",A="5px"===window.getComputedStyle(e,":before").getPropertyValue("font-size")}),A}),Modernizr.addTest("userselect",testAllProps("userSelect","none",!0)),testRunner(),setClasses(classes),delete ModernizrProto.addTest,delete ModernizrProto.addAsyncTest;for(var i=0;i d; d++) + if ( + ((s = A[d]), + (l = mStyle.style[s]), + contains(s, '-') && (s = cssToDOM(s)), + mStyle.style[s] !== undefined) + ) { + if (r || is(t, 'undefined')) return (n(), 'pfx' == e ? s : !0); + try { + mStyle.style[s] = t; + } catch (u) {} + if (mStyle.style[s] != l) return (n(), 'pfx' == e ? s : !0); + } + return (n(), !1); + } + function fnBind(A, e) { + return function () { + return A.apply(e, arguments); + }; + } + function testDOMProps(A, e, t) { + var r; + for (var n in A) + if (A[n] in e) + return t === !1 + ? A[n] + : ((r = e[A[n]]), is(r, 'function') ? fnBind(r, t || e) : r); + return !1; + } + function testPropsAll(A, e, t, r, n) { + var o = A.charAt(0).toUpperCase() + A.slice(1), + i = (A + ' ' + cssomPrefixes.join(o + ' ') + o).split(' '); + return is(e, 'string') || is(e, 'undefined') + ? testProps(i, e, r, n) + : ((i = (A + ' ' + domPrefixes.join(o + ' ') + o).split(' ')), + testDOMProps(i, e, t)); + } + function detectDeleteDatabase(A, e) { + var t = A.deleteDatabase(e); + ((t.onsuccess = function () { + addTest('indexeddb.deletedatabase', !0); + }), + (t.onerror = function () { + addTest('indexeddb.deletedatabase', !1); + })); + } + function testAllProps(A, e, t) { + return testPropsAll(A, undefined, undefined, e, t); + } + var classes = [], + tests = [], + ModernizrProto = { + _version: '3.5.0', + _config: { + classPrefix: '', + enableClasses: !0, + enableJSClass: !0, + usePrefixes: !0 + }, + _q: [], + on: function (A, e) { + var t = this; + setTimeout(function () { + e(t[A]); + }, 0); + }, + addTest: function (A, e, t) { + tests.push({ name: A, fn: e, options: t }); + }, + addAsyncTest: function (A) { + tests.push({ name: null, fn: A }); + } + }, + Modernizr = function () {}; + ((Modernizr.prototype = ModernizrProto), + (Modernizr = new Modernizr()), + Modernizr.addTest('applicationcache', 'applicationCache' in window), + Modernizr.addTest( + 'blobconstructor', + function () { + try { + return !!new Blob(); + } catch (A) { + return !1; + } + }, + { aliases: ['blob-constructor'] } + ), + Modernizr.addTest('cookies', function () { + try { + document.cookie = 'cookietest=1'; + var A = -1 != document.cookie.indexOf('cookietest='); + return ( + (document.cookie = + 'cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT'), + A + ); + } catch (e) { + return !1; + } + }), + Modernizr.addTest( + 'cors', + 'XMLHttpRequest' in window && 'withCredentials' in new XMLHttpRequest() + ), + Modernizr.addTest('customprotocolhandler', function () { + if (!navigator.registerProtocolHandler) return !1; + try { + navigator.registerProtocolHandler('thisShouldFail'); + } catch (A) { + return A instanceof TypeError; + } + return !1; + }), + Modernizr.addTest( + 'customevent', + 'CustomEvent' in window && 'function' == typeof window.CustomEvent + ), + Modernizr.addTest( + 'dataview', + 'undefined' != typeof DataView && 'getFloat64' in DataView.prototype + ), + Modernizr.addTest('eventlistener', 'addEventListener' in window), + Modernizr.addTest('geolocation', 'geolocation' in navigator), + Modernizr.addTest('history', function () { + var A = navigator.userAgent; + return (-1 === A.indexOf('Android 2.') && + -1 === A.indexOf('Android 4.0')) || + -1 === A.indexOf('Mobile Safari') || + -1 !== A.indexOf('Chrome') || + -1 !== A.indexOf('Windows Phone') || + 'file:' === location.protocol + ? window.history && 'pushState' in window.history + : !1; + }), + Modernizr.addTest( + 'ie8compat', + !window.addEventListener && + !!document.documentMode && + 7 === document.documentMode + ), + Modernizr.addTest( + 'json', + 'JSON' in window && 'parse' in JSON && 'stringify' in JSON + ), + Modernizr.addTest('notification', function () { + if (!window.Notification || !window.Notification.requestPermission) + return !1; + if ('granted' === window.Notification.permission) return !0; + try { + new window.Notification(''); + } catch (A) { + if ('TypeError' === A.name) return !1; + } + return !0; + }), + Modernizr.addTest('postmessage', 'postMessage' in window), + Modernizr.addTest( + 'queryselector', + 'querySelector' in document && 'querySelectorAll' in document + ), + Modernizr.addTest('serviceworker', 'serviceWorker' in navigator), + Modernizr.addTest( + 'svg', + !!document.createElementNS && + !!document.createElementNS('http://www.w3.org/2000/svg', 'svg') + .createSVGRect + ), + Modernizr.addTest('templatestrings', function () { + var supports; + try { + (eval('``'), (supports = !0)); + } catch (e) {} + return !!supports; + }), + Modernizr.addTest('typedarrays', 'ArrayBuffer' in window)); + var supports = !1; + try { + supports = 'WebSocket' in window && 2 === window.WebSocket.CLOSING; + } catch (e) {} + (Modernizr.addTest('websockets', supports), + Modernizr.addTest('xdomainrequest', 'XDomainRequest' in window), + Modernizr.addTest('webaudio', function () { + var A = 'webkitAudioContext' in window, + e = 'AudioContext' in window; + return Modernizr._config.usePrefixes ? A || e : e; + })); + var CSS = window.CSS; + Modernizr.addTest('cssescape', CSS ? 'function' == typeof CSS.escape : !1); + var newSyntax = 'CSS' in window && 'supports' in window.CSS, + oldSyntax = 'supportsCSS' in window; + (Modernizr.addTest('supports', newSyntax || oldSyntax), + Modernizr.addTest('target', function () { + var A = window.document; + if (!('querySelectorAll' in A)) return !1; + try { + return (A.querySelectorAll(':target'), !0); + } catch (e) { + return !1; + } + }), + Modernizr.addTest('microdata', 'getItems' in document), + Modernizr.addTest( + 'mutationobserver', + !!window.MutationObserver || !!window.WebKitMutationObserver + ), + Modernizr.addTest('picture', 'HTMLPictureElement' in window), + Modernizr.addTest('es5array', function () { + return !!( + Array.prototype && + Array.prototype.every && + Array.prototype.filter && + Array.prototype.forEach && + Array.prototype.indexOf && + Array.prototype.lastIndexOf && + Array.prototype.map && + Array.prototype.some && + Array.prototype.reduce && + Array.prototype.reduceRight && + Array.isArray + ); + }), + Modernizr.addTest('es5date', function () { + var A = '2013-04-12T06:06:37.307Z', + e = !1; + try { + e = !!Date.parse(A); + } catch (t) {} + return !!( + Date.now && + Date.prototype && + Date.prototype.toISOString && + Date.prototype.toJSON && + e + ); + }), + Modernizr.addTest('es5function', function () { + return !(!Function.prototype || !Function.prototype.bind); + }), + Modernizr.addTest('es5object', function () { + return !!( + Object.keys && + Object.create && + Object.getPrototypeOf && + Object.getOwnPropertyNames && + Object.isSealed && + Object.isFrozen && + Object.isExtensible && + Object.getOwnPropertyDescriptor && + Object.defineProperty && + Object.defineProperties && + Object.seal && + Object.freeze && + Object.preventExtensions + ); + }), + Modernizr.addTest( + 'strictmode', + (function () { + 'use strict'; + return !this; + })() + ), + Modernizr.addTest('es5string', function () { + return !(!String.prototype || !String.prototype.trim); + }), + Modernizr.addTest('es5syntax', function () { + var value, + obj, + stringAccess, + getter, + setter, + reservedWords, + zeroWidthChars; + try { + return ( + (stringAccess = eval('"foobar"[3] === "b"')), + (getter = eval('({ get x(){ return 1 } }).x === 1')), + eval('({ set x(v){ value = v; } }).x = 1'), + (setter = 1 === value), + eval('obj = ({ if: 1 })'), + (reservedWords = 1 === obj['if']), + (zeroWidthChars = eval('_‌‍ = true')), + stringAccess && getter && setter && reservedWords && zeroWidthChars + ); + } catch (ignore) { + return !1; + } + }), + Modernizr.addTest('es5undefined', function () { + var A, e; + try { + ((e = window.undefined), + (window.undefined = 12345), + (A = 'undefined' == typeof window.undefined), + (window.undefined = e)); + } catch (t) { + return !1; + } + return A; + }), + Modernizr.addTest('es5', function () { + return !!( + Modernizr.es5array && + Modernizr.es5date && + Modernizr.es5function && + Modernizr.es5object && + Modernizr.strictmode && + Modernizr.es5string && + Modernizr.json && + Modernizr.es5syntax && + Modernizr.es5undefined + ); + }), + Modernizr.addTest( + 'es6array', + !!( + Array.prototype && + Array.prototype.copyWithin && + Array.prototype.fill && + Array.prototype.find && + Array.prototype.findIndex && + Array.prototype.keys && + Array.prototype.entries && + Array.prototype.values && + Array.from && + Array.of + ) + ), + Modernizr.addTest('generators', function () { + try { + new Function('function* test() {}')(); + } catch (A) { + return !1; + } + return !0; + }), + Modernizr.addTest( + 'es6math', + !!( + Math && + Math.clz32 && + Math.cbrt && + Math.imul && + Math.sign && + Math.log10 && + Math.log2 && + Math.log1p && + Math.expm1 && + Math.cosh && + Math.sinh && + Math.tanh && + Math.acosh && + Math.asinh && + Math.atanh && + Math.hypot && + Math.trunc && + Math.fround + ) + ), + Modernizr.addTest( + 'es6number', + !!( + Number.isFinite && + Number.isInteger && + Number.isSafeInteger && + Number.isNaN && + Number.parseInt && + Number.parseFloat && + Number.isInteger(Number.MAX_SAFE_INTEGER) && + Number.isInteger(Number.MIN_SAFE_INTEGER) && + Number.isFinite(Number.EPSILON) + ) + ), + Modernizr.addTest( + 'es6string', + !!( + String.fromCodePoint && + String.raw && + String.prototype.codePointAt && + String.prototype.repeat && + String.prototype.startsWith && + String.prototype.endsWith && + String.prototype.includes + ) + ), + Modernizr.addTest('promises', function () { + return ( + 'Promise' in window && + 'resolve' in window.Promise && + 'reject' in window.Promise && + 'all' in window.Promise && + 'race' in window.Promise && + (function () { + var A; + return ( + new window.Promise(function (e) { + A = e; + }), + 'function' == typeof A + ); + })() + ); + }), + Modernizr.addTest('devicemotion', 'DeviceMotionEvent' in window), + Modernizr.addTest('deviceorientation', 'DeviceOrientationEvent' in window), + Modernizr.addTest( + 'es6object', + !!(Object.assign && Object.is && Object.setPrototypeOf) + ), + Modernizr.addTest( + 'filereader', + !!(window.File && window.FileList && window.FileReader) + ), + Modernizr.addTest('beacon', 'sendBeacon' in navigator), + Modernizr.addTest('lowbandwidth', function () { + var A = navigator.connection || { type: 0 }; + return 3 == A.type || 4 == A.type || /^[23]g$/.test(A.type); + }), + Modernizr.addTest('eventsource', 'EventSource' in window), + Modernizr.addTest('fetch', 'fetch' in window), + Modernizr.addTest( + 'xhrresponsetype', + (function () { + if ('undefined' == typeof XMLHttpRequest) return !1; + var A = new XMLHttpRequest(); + return (A.open('get', '/', !0), 'response' in A); + })() + ), + Modernizr.addTest( + 'xhr2', + 'XMLHttpRequest' in window && 'withCredentials' in new XMLHttpRequest() + ), + Modernizr.addTest('speechsynthesis', 'SpeechSynthesisUtterance' in window), + Modernizr.addTest('localstorage', function () { + var A = 'modernizr'; + try { + return (localStorage.setItem(A, A), localStorage.removeItem(A), !0); + } catch (e) { + return !1; + } + }), + Modernizr.addTest('sessionstorage', function () { + var A = 'modernizr'; + try { + return (sessionStorage.setItem(A, A), sessionStorage.removeItem(A), !0); + } catch (e) { + return !1; + } + }), + Modernizr.addTest('websqldatabase', 'openDatabase' in window), + Modernizr.addTest('svgfilters', function () { + var A = !1; + try { + A = + 'SVGFEColorMatrixElement' in window && + 2 == SVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_SATURATE; + } catch (e) {} + return A; + }), + Modernizr.addTest('urlparser', function () { + var A; + try { + return ( + (A = new URL('http://modernizr.com/')), + 'http://modernizr.com/' === A.href + ); + } catch (e) { + return !1; + } + }), + Modernizr.addTest('websocketsbinary', function () { + var A, + e = 'https:' == location.protocol ? 'wss' : 'ws'; + if ('WebSocket' in window) { + if ((A = 'binaryType' in WebSocket.prototype)) return A; + try { + return !!new WebSocket(e + '://.').binaryType; + } catch (t) {} + } + return !1; + }), + Modernizr.addTest('atobbtoa', 'atob' in window && 'btoa' in window, { + aliases: ['atob-btoa'] + }), + Modernizr.addTest('framed', window.location != top.location), + Modernizr.addTest('sharedworkers', 'SharedWorker' in window), + Modernizr.addTest('webworkers', 'Worker' in window)); + var prefixes = ModernizrProto._config.usePrefixes + ? ' -webkit- -moz- -o- -ms- '.split(' ') + : ['', '']; + ((ModernizrProto._prefixes = prefixes), + Modernizr.addTest('contains', is(String.prototype.contains, 'function'))); + var docElement = document.documentElement; + (Modernizr.addTest( + 'contextmenu', + 'contextMenu' in docElement && 'HTMLMenuItemElement' in window + ), + Modernizr.addTest('cssall', 'all' in docElement.style), + Modernizr.addTest('willchange', 'willChange' in docElement.style), + Modernizr.addTest('classlist', 'classList' in docElement), + Modernizr.addTest('documentfragment', function () { + return ( + 'createDocumentFragment' in document && 'appendChild' in docElement + ); + })); + var isSVG = 'svg' === docElement.nodeName.toLowerCase(), + html5; + isSVG || + !(function (A, e) { + function t(A, e) { + var t = A.createElement('p'), + r = A.getElementsByTagName('head')[0] || A.documentElement; + return ( + (t.innerHTML = 'x'), + r.insertBefore(t.lastChild, r.firstChild) + ); + } + function r() { + var A = h.elements; + return 'string' == typeof A ? A.split(' ') : A; + } + function n(A, e) { + var t = h.elements; + ('string' != typeof t && (t = t.join(' ')), + 'string' != typeof A && (A = A.join(' ')), + (h.elements = t + ' ' + A), + s(e)); + } + function o(A) { + var e = E[A[f]]; + return (e || ((e = {}), g++, (A[f] = g), (E[g] = e)), e); + } + function i(A, t, r) { + if ((t || (t = e), c)) return t.createElement(A); + r || (r = o(t)); + var n; + return ( + (n = r.cache[A] + ? r.cache[A].cloneNode() + : m.test(A) + ? (r.cache[A] = r.createElem(A)).cloneNode() + : r.createElem(A)), + !n.canHaveChildren || w.test(A) || n.tagUrn + ? n + : r.frag.appendChild(n) + ); + } + function d(A, t) { + if ((A || (A = e), c)) return A.createDocumentFragment(); + t = t || o(A); + for ( + var n = t.frag.cloneNode(), i = 0, d = r(), a = d.length; + a > i; + i++ + ) + n.createElement(d[i]); + return n; + } + function a(A, e) { + (e.cache || + ((e.cache = {}), + (e.createElem = A.createElement), + (e.createFrag = A.createDocumentFragment), + (e.frag = e.createFrag())), + (A.createElement = function (t) { + return h.shivMethods ? i(t, A, e) : e.createElem(t); + }), + (A.createDocumentFragment = Function( + 'h,f', + 'return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&(' + + r() + .join() + .replace(/[\w\-:]+/g, function (A) { + return ( + e.createElem(A), e.frag.createElement(A), 'c("' + A + '")' + ); + }) + + ');return n}' + )(h, e.frag))); + } + function s(A) { + A || (A = e); + var r = o(A); + return ( + !h.shivCSS || + l || + r.hasCSS || + (r.hasCSS = !!t( + A, + 'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}' + )), + c || a(A, r), + A + ); + } + var l, + c, + u = '3.7.3', + p = A.html5 || {}, + w = + /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i, + m = + /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i, + f = '_html5shiv', + g = 0, + E = {}; + !(function () { + try { + var A = e.createElement('a'); + ((A.innerHTML = ''), + (l = 'hidden' in A), + (c = + 1 == A.childNodes.length || + (function () { + e.createElement('a'); + var A = e.createDocumentFragment(); + return ( + 'undefined' == typeof A.cloneNode || + 'undefined' == typeof A.createDocumentFragment || + 'undefined' == typeof A.createElement + ); + })())); + } catch (t) { + ((l = !0), (c = !0)); + } + })(); + var h = { + elements: + p.elements || + 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video', + version: u, + shivCSS: p.shivCSS !== !1, + supportsUnknownElements: c, + shivMethods: p.shivMethods !== !1, + type: 'default', + shivDocument: s, + createElement: i, + createDocumentFragment: d, + addElements: n + }; + ((A.html5 = h), + s(e), + 'object' == typeof module && module.exports && (module.exports = h)); + })('undefined' != typeof window ? window : this, document); + var omPrefixes = 'Moz O ms Webkit', + domPrefixes = ModernizrProto._config.usePrefixes + ? omPrefixes.toLowerCase().split(' ') + : []; + ModernizrProto._domPrefixes = domPrefixes; + var hasOwnProp; + (!(function () { + var A = {}.hasOwnProperty; + hasOwnProp = + is(A, 'undefined') || is(A.call, 'undefined') + ? function (A, e) { + return e in A && is(A.constructor.prototype[e], 'undefined'); + } + : function (e, t) { + return A.call(e, t); + }; + })(), + (ModernizrProto._l = {}), + (ModernizrProto.on = function (A, e) { + (this._l[A] || (this._l[A] = []), + this._l[A].push(e), + Modernizr.hasOwnProperty(A) && + setTimeout(function () { + Modernizr._trigger(A, Modernizr[A]); + }, 0)); + }), + (ModernizrProto._trigger = function (A, e) { + if (this._l[A]) { + var t = this._l[A]; + (setTimeout(function () { + var A, r; + for (A = 0; A < t.length; A++) (r = t[A])(e); + }, 0), + delete this._l[A]); + } + }), + Modernizr._q.push(function () { + ModernizrProto.addTest = addTest; + }), + Modernizr.addAsyncTest(function () { + var A = new Image(); + ((A.onerror = function () { + addTest('exiforientation', !1, { aliases: ['exif-orientation'] }); + }), + (A.onload = function () { + addTest('exiforientation', 2 !== A.width, { + aliases: ['exif-orientation'] + }); + }), + (A.src = + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/4QAiRXhpZgAASUkqAAgAAAABABIBAwABAAAABgASAAAAAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAABAAIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+/iiiigD/2Q==')); + }), + Modernizr.addAsyncTest(function () { + function A() { + (clearTimeout(e), + window.removeEventListener('deviceproximity', A), + addTest('proximity', !0)); + } + var e, + t = 300; + 'ondeviceproximity' in window && 'onuserproximity' in window + ? (window.addEventListener('deviceproximity', A), + (e = setTimeout(function () { + (window.removeEventListener('deviceproximity', A), + addTest('proximity', !1)); + }, t))) + : addTest('proximity', !1); + }), + Modernizr.addAsyncTest(function () { + var A = new Image(); + ((A.onload = A.onerror = + function () { + addTest('jpeg2000', 1 == A.width); + }), + (A.src = + 'data:image/jp2;base64,/0//UQAyAAAAAAABAAAAAgAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAEBwEBBwEBBwEBBwEB/1IADAAAAAEAAAQEAAH/XAAEQED/ZAAlAAFDcmVhdGVkIGJ5IE9wZW5KUEVHIHZlcnNpb24gMi4wLjD/kAAKAAAAAABYAAH/UwAJAQAABAQAAf9dAAUBQED/UwAJAgAABAQAAf9dAAUCQED/UwAJAwAABAQAAf9dAAUDQED/k8+kEAGvz6QQAa/PpBABr994EAk//9k=')); + }), + Modernizr.addAsyncTest(function () { + var A = new Image(); + ((A.onload = A.onerror = + function () { + addTest('jpegxr', 1 == A.width, { aliases: ['jpeg-xr'] }); + }), + (A.src = + 'data:image/vnd.ms-photo;base64,SUm8AQgAAAAFAAG8AQAQAAAASgAAAIC8BAABAAAAAQAAAIG8BAABAAAAAQAAAMC8BAABAAAAWgAAAMG8BAABAAAAHwAAAAAAAAAkw91vA07+S7GFPXd2jckNV01QSE9UTwAZAYBxAAAAABP/gAAEb/8AAQAAAQAAAA==')); + }), + Modernizr.addAsyncTest(function () { + var A = new Image(); + ((A.onerror = function () { + addTest('webpalpha', !1, { aliases: ['webp-alpha'] }); + }), + (A.onload = function () { + addTest('webpalpha', 1 == A.width, { aliases: ['webp-alpha'] }); + }), + (A.src = + 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==')); + }), + Modernizr.addAsyncTest(function () { + var A = new Image(); + ((A.onerror = function () { + addTest('webpanimation', !1, { aliases: ['webp-animation'] }); + }), + (A.onload = function () { + addTest('webpanimation', 1 == A.width, { + aliases: ['webp-animation'] + }); + }), + (A.src = + 'data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA')); + }), + Modernizr.addAsyncTest(function () { + function A(A, e, t) { + function r(e) { + var r = e && 'load' === e.type ? 1 == n.width : !1, + o = 'webp' === A; + (addTest(A, o && r ? new Boolean(r) : r), t && t(e)); + } + var n = new Image(); + ((n.onerror = r), (n.onload = r), (n.src = e)); + } + var e = [ + { + uri: 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=', + name: 'webp' + }, + { + uri: 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==', + name: 'webp.alpha' + }, + { + uri: 'data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA', + name: 'webp.animation' + }, + { + uri: 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=', + name: 'webp.lossless' + } + ], + t = e.shift(); + A(t.name, t.uri, function (t) { + if (t && 'load' === t.type) + for (var r = 0; r < e.length; r++) A(e[r].name, e[r].uri); + }); + }), + Modernizr.addTest( + 'svgasimg', + document.implementation.hasFeature( + 'http://www.w3.org/TR/SVG11/feature#Image', + '1.1' + ) + ), + Modernizr.addAsyncTest(function () { + function A() { + var A = new Image(); + ((A.onerror = function () { + (addTest('datauri', !0), + (Modernizr.datauri = new Boolean(!0)), + (Modernizr.datauri.over32kb = !1)); + }), + (A.onload = function () { + (addTest('datauri', !0), + (Modernizr.datauri = new Boolean(!0)), + (Modernizr.datauri.over32kb = 1 == A.width && 1 == A.height)); + })); + for ( + var e = 'R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; + e.length < 33e3; + ) + e = '\r\n' + e; + A.src = 'data:image/gif;base64,' + e; + } + -1 !== navigator.userAgent.indexOf('MSIE 7.') && + setTimeout(function () { + addTest('datauri', !1); + }, 10); + var e = new Image(); + ((e.onerror = function () { + addTest('datauri', !1); + }), + (e.onload = function () { + 1 == e.width && 1 == e.height ? A() : addTest('datauri', !1); + }), + (e.src = + 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==')); + }), + Modernizr.addAsyncTest(function () { + function A() { + (addTest('blobworkers', !1), e()); + } + function e() { + (d && r.revokeObjectURL(d), i && i.terminate(), a && clearTimeout(a)); + } + try { + var t = window.BlobBuilder, + r = window.URL; + Modernizr._config.usePrefix && + ((t = + t || + window.MozBlobBuilder || + window.WebKitBlobBuilder || + window.MSBlobBuilder || + window.OBlobBuilder), + (r = + r || + window.MozURL || + window.webkitURL || + window.MSURL || + window.OURL)); + var n, + o, + i, + d, + a, + s = 'Modernizr', + l = 'this.onmessage=function(e){postMessage(e.data)}'; + try { + n = new Blob([l], { type: 'text/javascript' }); + } catch (c) {} + (n || ((o = new t()), o.append(l), (n = o.getBlob())), + (d = r.createObjectURL(n)), + (i = new Worker(d)), + (i.onmessage = function (A) { + (addTest('blobworkers', s === A.data), e()); + }), + (i.onerror = A), + (a = setTimeout(A, 200)), + i.postMessage(s)); + } catch (c) { + A(); + } + }), + Modernizr.addAsyncTest(function () { + try { + var A = 'Modernizr', + e = new Worker( + 'data:text/javascript;base64,dGhpcy5vbm1lc3NhZ2U9ZnVuY3Rpb24oZSl7cG9zdE1lc3NhZ2UoZS5kYXRhKX0=' + ); + ((e.onmessage = function (t) { + (e.terminate(), addTest('dataworkers', A === t.data), (e = null)); + }), + (e.onerror = function () { + (addTest('dataworkers', !1), (e = null)); + }), + setTimeout(function () { + addTest('dataworkers', !1); + }, 200), + e.postMessage(A)); + } catch (t) { + setTimeout(function () { + addTest('dataworkers', !1); + }, 0); + } + })); + var cssomPrefixes = ModernizrProto._config.usePrefixes + ? omPrefixes.split(' ') + : []; + ModernizrProto._cssomPrefixes = cssomPrefixes; + var atRule = function (A) { + var e, + t = prefixes.length, + r = window.CSSRule; + if ('undefined' == typeof r) return undefined; + if (!A) return !1; + if ( + ((A = A.replace(/^@/, '')), + (e = A.replace(/-/g, '_').toUpperCase() + '_RULE'), + e in r) + ) + return '@' + A; + for (var n = 0; t > n; n++) { + var o = prefixes[n], + i = o.toUpperCase() + '_' + e; + if (i in r) return '@-' + o.toLowerCase() + '-' + A; + } + return !1; + }; + ModernizrProto.atRule = atRule; + var hasEvent = (function () { + function A(A, t) { + var r; + return A + ? ((t && 'string' != typeof t) || (t = createElement(t || 'div')), + (A = 'on' + A), + (r = A in t), + !r && + e && + (t.setAttribute || (t = createElement('div')), + t.setAttribute(A, ''), + (r = 'function' == typeof t[A]), + t[A] !== undefined && (t[A] = undefined), + t.removeAttribute(A)), + r) + : !1; + } + var e = !('onblur' in document.documentElement); + return A; + })(); + ((ModernizrProto.hasEvent = hasEvent), + Modernizr.addTest('ambientlight', hasEvent('devicelight', window)), + Modernizr.addTest('hashchange', function () { + return hasEvent('hashchange', window) === !1 + ? !1 + : document.documentMode === undefined || document.documentMode > 7; + }), + Modernizr.addTest('inputsearchevent', hasEvent('search')), + Modernizr.addTest('pointerevents', function () { + var A = !1, + e = domPrefixes.length; + for (A = Modernizr.hasEvent('pointerdown'); e-- && !A;) + hasEvent(domPrefixes[e] + 'pointerdown') && (A = !0); + return A; + })); + var prefixedCSSValue = function (A, e) { + var t = !1, + r = createElement('div'), + n = r.style; + if (A in n) { + var o = domPrefixes.length; + for (n[A] = e, t = n[A]; o-- && !t;) + ((n[A] = '-' + domPrefixes[o] + '-' + e), (t = n[A])); + } + return ('' === t && (t = !1), t); + }; + ((ModernizrProto.prefixedCSSValue = prefixedCSSValue), + Modernizr.addTest('audio', function () { + var A = createElement('audio'), + e = !1; + try { + ((e = !!A.canPlayType), + e && + ((e = new Boolean(e)), + (e.ogg = A.canPlayType('audio/ogg; codecs="vorbis"').replace( + /^no$/, + '' + )), + (e.mp3 = A.canPlayType('audio/mpeg; codecs="mp3"').replace( + /^no$/, + '' + )), + (e.opus = + A.canPlayType('audio/ogg; codecs="opus"') || + A.canPlayType('audio/webm; codecs="opus"').replace(/^no$/, '')), + (e.wav = A.canPlayType('audio/wav; codecs="1"').replace( + /^no$/, + '' + )), + (e.m4a = ( + A.canPlayType('audio/x-m4a;') || A.canPlayType('audio/aac;') + ).replace(/^no$/, '')))); + } catch (t) {} + return e; + }), + Modernizr.addTest('canvas', function () { + var A = createElement('canvas'); + return !(!A.getContext || !A.getContext('2d')); + }), + Modernizr.addTest('canvastext', function () { + return Modernizr.canvas === !1 + ? !1 + : 'function' == + typeof createElement('canvas').getContext('2d').fillText; + }), + Modernizr.addTest('contenteditable', function () { + if ('contentEditable' in docElement) { + var A = createElement('div'); + return ((A.contentEditable = !0), 'true' === A.contentEditable); + } + }), + Modernizr.addTest('emoji', function () { + if (!Modernizr.canvastext) return !1; + var A = window.devicePixelRatio || 1, + e = 12 * A, + t = createElement('canvas'), + r = t.getContext('2d'); + return ( + (r.fillStyle = '#f00'), + (r.textBaseline = 'top'), + (r.font = '32px Arial'), + r.fillText('🐨', 0, 0), + 0 !== r.getImageData(e, e, 1, 1).data[0] + ); + }), + addTest('htmlimports', 'import' in createElement('link')), + Modernizr.addTest('olreversed', 'reversed' in createElement('ol')), + Modernizr.addTest('userdata', !!createElement('div').addBehavior), + Modernizr.addTest('video', function () { + var A = createElement('video'), + e = !1; + try { + ((e = !!A.canPlayType), + e && + ((e = new Boolean(e)), + (e.ogg = A.canPlayType('video/ogg; codecs="theora"').replace( + /^no$/, + '' + )), + (e.h264 = A.canPlayType('video/mp4; codecs="avc1.42E01E"').replace( + /^no$/, + '' + )), + (e.webm = A.canPlayType('video/webm; codecs="vp8, vorbis"').replace( + /^no$/, + '' + )), + (e.vp9 = A.canPlayType('video/webm; codecs="vp9"').replace( + /^no$/, + '' + )), + (e.hls = A.canPlayType( + 'application/x-mpegURL; codecs="avc1.42E01E"' + ).replace(/^no$/, '')))); + } catch (t) {} + return e; + }), + Modernizr.addTest('vml', function () { + var A, + e = createElement('div'), + t = !1; + return ( + isSVG || + ((e.innerHTML = ''), + (A = e.firstChild), + 'style' in A && (A.style.behavior = 'url(#default#VML)'), + (t = A ? 'object' == typeof A.adj : !0)), + t + ); + }), + Modernizr.addTest('webanimations', 'animate' in createElement('div')), + Modernizr.addTest('webgl', function () { + var A = createElement('canvas'), + e = + 'probablySupportsContext' in A + ? 'probablySupportsContext' + : 'supportsContext'; + return e in A + ? A[e]('webgl') || A[e]('experimental-webgl') + : 'WebGLRenderingContext' in window; + }), + Modernizr.addTest( + 'adownload', + !window.externalHost && 'download' in createElement('a') + ), + Modernizr.addTest('audioloop', 'loop' in createElement('audio')), + Modernizr.addAsyncTest(function () { + function A(t) { + clearTimeout(e); + var n = t !== undefined && 'loadeddata' === t.type ? !0 : !1; + (r.removeEventListener('loadeddata', A, !1), + addTest('audiopreload', n), + r.parentNode.removeChild(r)); + } + var e, + t = 300, + r = createElement('audio'), + n = r.style; + if (!(Modernizr.audio && 'preload' in r)) + return void addTest('audiopreload', !1); + ((n.position = 'absolute'), (n.height = 0), (n.width = 0)); + try { + if (Modernizr.audio.mp3) + r.src = + 'data:audio/mpeg;base64,//MUxAAB6AXgAAAAAPP+c6nf//yi/6f3//MUxAMAAAIAAAjEcH//0fTX6C9Lf//0//MUxA4BeAIAAAAAAKX2/6zv//+IlR4f//MUxBMCMAH8AAAAABYWalVMQU1FMy45//MUxBUB0AH0AAAAADkuM1VVVVVVVVVV//MUxBgBUATowAAAAFVVVVVVVVVVVVVV'; + else if (Modernizr.audio.m4a) + r.src = + 'data:audio/x-m4a;base64,AAAAGGZ0eXBNNEEgAAACAGlzb21pc28yAAAACGZyZWUAAAAfbWRhdN4EAABsaWJmYWFjIDEuMjgAAAFoAQBHAAACiG1vb3YAAABsbXZoZAAAAAB8JbCAfCWwgAAAA+gAAAAYAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAG0dHJhawAAAFx0a2hkAAAAD3wlsIB8JbCAAAAAAQAAAAAAAAAYAAAAAAAAAAAAAAAAAQAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAABUG1kaWEAAAAgbWRoZAAAAAB8JbCAfCWwgAAArEQAAAQAVcQAAAAAAC1oZGxyAAAAAAAAAABzb3VuAAAAAAAAAAAAAAAAU291bmRIYW5kbGVyAAAAAPttaW5mAAAAEHNtaGQAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAL9zdGJsAAAAW3N0c2QAAAAAAAAAAQAAAEttcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAArEQAAAAAACdlc2RzAAAAAAMZAAEABBFAFQAAAAABftAAAAAABQISCAYBAgAAABhzdHRzAAAAAAAAAAEAAAABAAAEAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAAXAAAAAQAAABRzdGNvAAAAAAAAAAEAAAAoAAAAYHVkdGEAAABYbWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAraWxzdAAAACOpdG9vAAAAG2RhdGEAAAABAAAAAExhdmY1Mi42NC4y'; + else if (Modernizr.audio.ogg) + r.src = + 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAAD/QwAAAAAAAM2LVKsBHgF2b3JiaXMAAAAAAUSsAAAAAAAAgLsAAAAAAAC4AU9nZ1MAAAAAAAAAAAAA/0MAAAEAAADmvOe6Dy3/////////////////MgN2b3JiaXMdAAAAWGlwaC5PcmcgbGliVm9yYmlzIEkgMjAwNzA2MjIAAAAAAQV2b3JiaXMfQkNWAQAAAQAYY1QpRplS0kqJGXOUMUaZYpJKiaWEFkJInXMUU6k515xrrLm1IIQQGlNQKQWZUo5SaRljkCkFmVIQS0kldBI6J51jEFtJwdaYa4tBthyEDZpSTCnElFKKQggZU4wpxZRSSkIHJXQOOuYcU45KKEG4nHOrtZaWY4updJJK5yRkTEJIKYWSSgelU05CSDWW1lIpHXNSUmpB6CCEEEK2IIQNgtCQVQAAAQDAQBAasgoAUAAAEIqhGIoChIasAgAyAAAEoCiO4iiOIzmSY0kWEBqyCgAAAgAQAADAcBRJkRTJsSRL0ixL00RRVX3VNlVV9nVd13Vd13UgNGQVAAABAEBIp5mlGiDCDGQYCA1ZBQAgAAAARijCEANCQ1YBAAABAABiKDmIJrTmfHOOg2Y5aCrF5nRwItXmSW4q5uacc845J5tzxjjnnHOKcmYxaCa05pxzEoNmKWgmtOacc57E5kFrqrTmnHPGOaeDcUYY55xzmrTmQWo21uaccxa0pjlqLsXmnHMi5eZJbS7V5pxzzjnnnHPOOeecc6oXp3NwTjjnnHOi9uZabkIX55xzPhmne3NCOOecc84555xzzjnnnHOC0JBVAAAQAABBGDaGcacgSJ+jgRhFiGnIpAfdo8MkaAxyCqlHo6ORUuoglFTGSSmdIDRkFQAACAAAIYQUUkghhRRSSCGFFFKIIYYYYsgpp5yCCiqppKKKMsoss8wyyyyzzDLrsLPOOuwwxBBDDK20EktNtdVYY62555xrDtJaaa211koppZRSSikIDVkFAIAAABAIGWSQQUYhhRRSiCGmnHLKKaigAkJDVgEAgAAAAgAAADzJc0RHdERHdERHdERHdETHczxHlERJlERJtEzL1ExPFVXVlV1b1mXd9m1hF3bd93Xf93Xj14VhWZZlWZZlWZZlWZZlWZZlWYLQkFUAAAgAAIAQQgghhRRSSCGlGGPMMeegk1BCIDRkFQAACAAgAAAAwFEcxXEkR3IkyZIsSZM0S7M8zdM8TfREURRN01RFV3RF3bRF2ZRN13RN2XRVWbVdWbZt2dZtX5Zt3/d93/d93/d93/d93/d1HQgNWQUASAAA6EiOpEiKpEiO4ziSJAGhIasAABkAAAEAKIqjOI7jSJIkSZakSZ7lWaJmaqZneqqoAqEhqwAAQAAAAQAAAAAAKJriKabiKaLiOaIjSqJlWqKmaq4om7Lruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7rui4QGrIKAJAAANCRHMmRHEmRFEmRHMkBQkNWAQAyAAACAHAMx5AUybEsS9M8zdM8TfRET/RMTxVd0QVCQ1YBAIAAAAIAAAAAADAkw1IsR3M0SZRUS7VUTbVUSxVVT1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTVN0zRNIDRkJQAABADAYo3B5SAhJSXl3hDCEJOeMSYhtV4hBJGS3jEGFYOeMqIMct5C4xCDHggNWREARAEAAMYgxxBzyDlHqZMSOeeodJQa5xyljlJnKcWYYs0oldhSrI1zjlJHraOUYiwtdpRSjanGAgAAAhwAAAIshEJDVgQAUQAAhDFIKaQUYow5p5xDjCnnmHOGMeYcc44556B0UirnnHROSsQYc445p5xzUjonlXNOSiehAACAAAcAgAALodCQFQFAnACAQZI8T/I0UZQ0TxRFU3RdUTRd1/I81fRMU1U90VRVU1Vt2VRVWZY8zzQ901RVzzRV1VRVWTZVVZZFVdVt03V123RV3ZZt2/ddWxZ2UVVt3VRd2zdV1/Zd2fZ9WdZ1Y/I8VfVM03U903Rl1XVtW3VdXfdMU5ZN15Vl03Vt25VlXXdl2fc103Rd01Vl2XRd2XZlV7ddWfZ903WF35VlX1dlWRh2XfeFW9eV5XRd3VdlVzdWWfZ9W9eF4dZ1YZk8T1U903RdzzRdV3VdX1dd19Y105Rl03Vt2VRdWXZl2fddV9Z1zzRl2XRd2zZdV5ZdWfZ9V5Z13XRdX1dlWfhVV/Z1WdeV4dZt4Tdd1/dVWfaFV5Z14dZ1Ybl1XRg+VfV9U3aF4XRl39eF31luXTiW0XV9YZVt4VhlWTl+4ViW3feVZXRdX1ht2RhWWRaGX/id5fZ943h1XRlu3efMuu8Mx++k+8rT1W1jmX3dWWZfd47hGDq/8OOpqq+brisMpywLv+3rxrP7vrKMruv7qiwLvyrbwrHrvvP8vrAso+z6wmrLwrDatjHcvm4sv3Acy2vryjHrvlG2dXxfeArD83R1XXlmXcf2dXTjRzh+ygAAgAEHAIAAE8pAoSErAoA4AQCPJImiZFmiKFmWKIqm6LqiaLqupGmmqWmeaVqaZ5qmaaqyKZquLGmaaVqeZpqap5mmaJqua5qmrIqmKcumasqyaZqy7LqybbuubNuiacqyaZqybJqmLLuyq9uu7Oq6pFmmqXmeaWqeZ5qmasqyaZquq3meanqeaKqeKKqqaqqqraqqLFueZ5qa6KmmJ4qqaqqmrZqqKsumqtqyaaq2bKqqbbuq7Pqybeu6aaqybaqmLZuqatuu7OqyLNu6L2maaWqeZ5qa55mmaZqybJqqK1uep5qeKKqq5ommaqqqLJumqsqW55mqJ4qq6omea5qqKsumatqqaZq2bKqqLZumKsuubfu+68qybqqqbJuqauumasqybMu+78qq7oqmKcumqtqyaaqyLduy78uyrPuiacqyaaqybaqqLsuybRuzbPu6aJqybaqmLZuqKtuyLfu6LNu678qub6uqrOuyLfu67vqucOu6MLyybPuqrPq6K9u6b+sy2/Z9RNOUZVM1bdtUVVl2Zdn2Zdv2fdE0bVtVVVs2TdW2ZVn2fVm2bWE0Tdk2VVXWTdW0bVmWbWG2ZeF2Zdm3ZVv2ddeVdV/XfePXZd3murLty7Kt+6qr+rbu+8Jw667wCgAAGHAAAAgwoQwUGrISAIgCAACMYYwxCI1SzjkHoVHKOecgZM5BCCGVzDkIIZSSOQehlJQy5yCUklIIoZSUWgshlJRSawUAABQ4AAAE2KApsThAoSErAYBUAACD41iW55miatqyY0meJ4qqqaq27UiW54miaaqqbVueJ4qmqaqu6+ua54miaaqq6+q6aJqmqaqu67q6Lpqiqaqq67qyrpumqqquK7uy7Oumqqqq68quLPvCqrquK8uybevCsKqu68qybNu2b9y6ruu+7/vCka3rui78wjEMRwEA4AkOAEAFNqyOcFI0FlhoyEoAIAMAgDAGIYMQQgYhhJBSSiGllBIAADDgAAAQYEIZKDRkRQAQJwAAGEMppJRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkgppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkqppJRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoplVJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSCgCQinAAkHowoQwUGrISAEgFAACMUUopxpyDEDHmGGPQSSgpYsw5xhyUklLlHIQQUmktt8o5CCGk1FJtmXNSWosx5hgz56SkFFvNOYdSUoux5ppr7qS0VmuuNedaWqs115xzzbm0FmuuOdecc8sx15xzzjnnGHPOOeecc84FAOA0OACAHtiwOsJJ0VhgoSErAYBUAAACGaUYc8456BBSjDnnHIQQIoUYc845CCFUjDnnHHQQQqgYc8w5CCGEkDnnHIQQQgghcw466CCEEEIHHYQQQgihlM5BCCGEEEooIYQQQgghhBA6CCGEEEIIIYQQQgghhFJKCCGEEEIJoZRQAABggQMAQIANqyOcFI0FFhqyEgAAAgCAHJagUs6EQY5Bjw1BylEzDUJMOdGZYk5qMxVTkDkQnXQSGWpB2V4yCwAAgCAAIMAEEBggKPhCCIgxAABBiMwQCYVVsMCgDBoc5gHAA0SERACQmKBIu7iALgNc0MVdB0IIQhCCWBxAAQk4OOGGJ97whBucoFNU6iAAAAAAAAwA4AEA4KAAIiKaq7C4wMjQ2ODo8AgAAAAAABYA+AAAOD6AiIjmKiwuMDI0Njg6PAIAAAAAAAAAAICAgAAAAAAAQAAAAICAT2dnUwAE7AwAAAAAAAD/QwAAAgAAADuydfsFAQEBAQEACg4ODg=='; + else { + if (!Modernizr.audio.wav) return void addTest('audiopreload', !1); + r.src = + 'data:audio/wav;base64,UklGRvwZAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YdgZAAAAAAEA/v8CAP//AAABAP////8DAPz/BAD9/wEAAAAAAAAAAAABAP7/AgD//wAAAQD//wAAAQD//wAAAQD+/wIA//8AAAAAAAD//wIA/v8BAAAA//8BAAAA//8BAP//AQAAAP//AQD//wEAAAD//wEA//8BAP//AQD//wEA//8BAP//AQD+/wMA/f8DAP3/AgD+/wIA/////wMA/f8CAP7/AgD+/wMA/f8CAP7/AgD//wAAAAAAAAAAAQD+/wIA/v8CAP7/AwD9/wIA/v8BAAEA/v8CAP7/AQAAAAAAAAD//wEAAAD//wIA/f8DAP7/AQD//wEAAAD//wEA//8CAP7/AQD//wIA/v8CAP7/AQAAAAAAAAD//wEAAAAAAAAA//8BAP//AgD9/wQA+/8FAPz/AgAAAP//AgD+/wEAAAD//wIA/v8CAP3/BAD8/wQA/P8DAP7/AwD8/wQA/P8DAP7/AQAAAAAA//8BAP//AgD+/wEAAAD//wIA/v8BAP//AQD//wEAAAD//wEA//8BAAAAAAAAAP//AgD+/wEAAAAAAAAAAAD//wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AgD+/wIA/v8BAP//AQABAP7/AQD//wIA/v8CAP3/AwD/////AgD9/wMA/v8BAP//AQAAAP//AQD//wEA//8BAP//AAABAP//AAABAP//AQD//wAAAAACAP3/AwD9/wIA//8BAP//AQD//wEA//8BAP//AgD9/wMA/v8AAAIA/f8CAAAA/v8EAPv/BAD9/wIAAAD+/wQA+v8HAPr/BAD+/wEAAAD//wIA/f8EAPz/BAD7/wUA/P8EAPz/AwD+/wEAAAD//wEAAAAAAP//AgD8/wUA+/8FAPz/AwD9/wIA//8AAAEA/v8CAP//AQD//wAAAAABAP//AgD9/wMA/f8EAPz/AwD+/wAAAwD7/wUA/P8DAP7/AQAAAP//AgD+/wEAAQD+/wIA/v8BAAEA/v8CAP7/AQAAAP//AgD9/wMA/f8DAP7/AgD+/wEAAAAAAAEA//8AAAEA/v8DAP3/AgD//wEA//8BAP7/AwD9/wMA/v8BAP//AQAAAP//AgD9/wMA/v8BAP//AQAAAP//AgD+/wEAAQD+/wIA/////wIA//8AAAEA/f8DAP//AAABAP////8DAP3/AwD+/wEA//8BAP//AQAAAAAA//8BAP//AQD//wEA//8BAP//AAAAAAEA//8BAP7/AgD//wEA//8AAAAAAAAAAAAAAAD//wIA/v8BAAAA//8BAAEA/v8BAAAA//8DAPz/AwD+/wIA/v8CAP3/AwD+/wEAAAD//wEA//8BAAAA//8BAAAA/v8EAPv/BAD+/wAAAAABAP7/AgD//wAAAAABAP7/AgD//wAAAAAAAAAAAAABAP3/BAD8/wQA/f8BAAAAAAABAP7/AgD+/wIA/v8CAP7/AgD+/wIA/v8BAAAAAAD//wIA/f8DAP7/AAABAP//AAACAPz/BAD9/wIA//8AAP//AwD9/wMA/P8EAP3/AwD9/wIA//8BAP//AQD+/wMA/f8DAP7/AAABAP//AQAAAP//AQD//wIA/f8DAP7/AQAAAP//AQAAAAAA//8CAP7/AQABAP7/AgD+/wEAAQD+/wIA/v8CAP////8CAP7/AgD//wAAAAABAP7/AwD9/wIAAAD+/wMA/f8CAP//AQD+/wMA/f8CAP//AAACAPz/BQD6/wUA/v///wIA/v8CAP3/BAD7/wYA+v8FAPz/AwD/////AgD+/wEAAAD//wEAAAD//wIA/f8DAP7/AQAAAP//AgD//wAA//8BAAAAAAAAAP//AQD//wEA//8AAAIA/f8DAP3/AgAAAP//AQD//wEA//8AAAEA//8BAP////8CAP//AAABAP3/BAD9/wIA/v8BAAEA//8BAP7/AgD//wEA//8AAAEA//8BAP//AAAAAAEA//8BAP7/AgD//wEA//8AAAAAAQD+/wIA/v8BAAAAAAD//wIA/v8BAAAAAAAAAAAAAQD+/wMA/f8CAP//AQD//wIA/f8DAP7/AQD//wEA//8CAP7/AAABAP7/AwD9/wMA/v8AAAEA//8BAAAAAAD//wIA/v8BAAAA//8CAP7/AgD+/wEA//8CAP7/AgD//wAAAAAAAAAAAQD//wEA/v8DAPz/BQD8/wIA//8AAAEAAAD//wEA//8BAP//AQAAAAAA//8BAP//AgD+/wEAAAAAAP//AQD+/wMA/////wEA/v8CAP//AQD//wEA//8AAAEA//8BAAAA/v8EAPz/AwD+/wEAAAAAAAAA//8CAP7/AQD//wEA//8BAP//AAABAP7/AwD9/wIA//8BAP//AQD//wEA//8AAAEA/v8EAPv/BAD9/wIA//8BAP7/AwD9/wIA//8AAAEA//8BAP//AQD//wAAAQD//wEAAAD+/wMA/v8AAAIA/f8DAP7/AQD//wAAAQD+/wMA/f8CAP//AAABAP7/AgD+/wMA/f8CAP7/AQABAP7/AgD+/wIA/v8CAP7/AwD8/wMA//8AAAEA//8AAAAAAAABAP//AQD//wAAAQD//wIA/f8DAP3/AwD+/wAAAgD9/wIA//8AAAEAAAD+/wMA/P8FAPv/BAD9/wIA//8AAP//AgD+/wIA/v8BAAAAAAD//wEAAAAAAP//AQD//wEA//8BAP//AAABAP7/AwD9/wIA//8BAP//AAABAP//AQD//wAAAQD//wEA//8BAP//AAABAAAA//8BAP7/AwD9/wMA/f8DAP3/AgD//wEA//8BAP7/AgD//wAAAgD8/wQA/f8CAP//AQD+/wMA/f8CAP7/AgD//wAAAAAAAAAAAAABAP7/AwD9/wIA/v8DAP3/AwD9/wIA/v8DAPz/BQD7/wQA/f8CAP7/AwD9/wMA/f8CAP//AQAAAP7/AwD+/wEA//8AAAEAAAAAAP//AAABAP//AQAAAP7/AwD9/wMA/f8CAP//AQD//wEA//8AAAIA/f8CAAAA//8BAAAA//8BAAAA/v8EAPv/BAD9/wIA//8AAAEA/v8CAP//AAABAP//AAABAP//AAABAP7/AwD8/wQA/f8CAAAA/v8DAP3/AwD9/wMA/v8BAAAA//8BAAAA//8CAP7/AQAAAAAAAAAAAAAA//8CAP7/AgD+/wIA/v8CAP7/AgD//wAAAQD//wAAAQD//wAAAQD//wAAAQD+/wIA//8AAAAAAQD+/wMA/f8CAP//AQD//wEA//8AAAEA/v8DAP3/AgD//wAAAAABAP7/AwD9/wIA//8AAAEA/v8DAP3/AgD//wAAAAABAP7/AwD8/wMA/v8CAP//AAD//wIA/v8CAP7/AQABAP7/AQAAAP//AgD/////AQD//wEAAAD//wEA/v8EAPv/BAD9/wMA/v8BAAAA//8BAAEA/P8GAPr/BQD8/wMA/v8BAAAA//8CAP7/AQABAP3/BAD7/wYA+/8EAPz/AwD//wEA//8BAP7/BAD8/wMA/v8AAAIA/v8BAAAA//8BAAAA//8BAAAA//8CAP3/AwD+/wAAAgD8/wUA/P8DAP7/AAABAAAAAAD//wEAAAD//wIA/f8DAP7/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA/f8EAPz/AwD/////AgD+/wIA/f8DAP7/AgD+/wEA//8CAP7/AQD//wEAAAAAAP//AQAAAP//AgD9/wMA/v8BAAAA//8BAP//AQAAAP//AAACAP3/BAD7/wQA/v8BAAAA//8BAP//AQAAAP//AQAAAP7/BAD7/wUA+/8EAP3/AgD//wAAAQD+/wIA//8AAAEA/v8CAP//AQD+/wEAAAAAAAAAAAD//wEA//8CAP3/AwD9/wIA//8AAAAAAAAAAAAA//8BAP//AgD+/wEA//8CAP7/AQAAAP//AgD/////AgD/////AgD+/wIA//8AAP//AQABAP7/AgD9/wMA/v8CAP////8BAAAAAAAAAAAA//8CAP////8DAPz/AwD+/wEAAAAAAP//AQD//wEAAAD//wEAAAD+/wQA+/8FAPz/AgAAAP//AgD9/wMA/v8BAAAAAAD//wEAAAD//wIA/v8BAAAAAAD//wIA/v8BAAAA//8BAAAA//8CAP7/AQD//wEA//8BAAAA//8BAP//AAABAP//AQAAAP7/AgD//wEA//8AAAAAAQD+/wMA/P8EAP7///8DAPz/BQD8/wEAAQD+/wMA/v8AAAEA//8BAP//AQD//wEA/v8CAP//AQD//wAAAAABAAAA//8BAP//AQAAAAAA//8BAP//AgD+/wAAAQD//wIA/f8CAP//AQAAAP7/AwD9/wMA/v8BAP//AAABAP//AgD9/wIA//8BAAAA//8BAAAA//8CAP3/AwD+/wEAAAD+/wQA/P8DAP7/AAACAP7/AQAAAP//AQAAAP//AQAAAP//AgD9/wIAAAD//wIA/f8DAP7/AQD//wEA//8CAP7/AQD//wAAAQD//wEA//8AAAAAAQD//wEAAAD9/wUA+/8FAPz/AgD//wAAAQD//wAAAQD+/wMA/f8BAAEA/v8CAP7/AgD+/wIA/v8BAAAAAAAAAAAAAAD//wIA/v8CAP////8CAP7/AgD+/wIA/v8CAP7/AQAAAP//AQAAAP//AQD//wAAAQD//wAAAQD+/wMA/f8CAAAA/v8DAP3/AgAAAP//AQAAAP7/AwD9/wMA/v8BAP//AQD//wEAAAD+/wMA/f8CAAAA/v8CAP//AAAAAAEA//8AAAEA/v8DAP3/AwD9/wIA//8BAP//AgD8/wQA/v8BAAAA/v8CAP//AQD//wAAAAAAAAEA/f8EAPz/BAD9/wIA//8AAAAAAAABAP//AAAAAAAAAAABAP3/BAD9/wIA/v8BAAEA//8AAAAA//8CAP7/AgD9/wQA+/8FAPv/BQD8/wMA/f8DAP3/AwD+/wAAAgD9/wMA/f8CAAAA/v8EAPv/BQD7/wUA/P8DAP///v8DAP3/BAD8/wMA/f8DAP7/AQD//wEAAAD//wEA/v8CAAAA/v8CAP7/AgD//wAAAAAAAAAAAQD+/wIA//8AAAEA/v8DAPz/BAD9/wIA//8AAP//AgD//wEA/v8BAAAAAQD//wAAAAAAAAEA//8AAAEA//8BAP//AAABAP//AQD+/wIA/v8DAPz/BAD8/wQA/f8BAAAAAQD+/wMA/P8DAP//AAAAAAAAAAD//wMA+/8FAP3/AQABAP3/BAD8/wMA/v8BAAAA//8CAP3/AwD+/wEAAQD9/wMA/f8EAPz/BAD7/wQA/v8BAAEA/f8DAP7/AQAAAP//AgD+/wEAAAD//wIA/v8CAP7/AgD+/wEAAQD//wEA/v8CAP7/BAD7/wQA/f8CAAAA//8AAAAAAAABAP//AQD+/wEAAQD+/wMA/f8BAAEA/v8DAPz/AwD/////AwD8/wQA/P8DAP7/AgD//wAA//8BAAAAAAAAAP//AgD+/wEAAAD//wIA/v8BAAAA//8CAP3/AgD//wAAAQD+/wIA/v8BAAAA//8CAP7/AgD+/wEA//8CAP3/BAD7/wQA/v8BAAAA//8AAAEAAAD//wIA/f8DAP7/AgD+/wIA/v8CAP7/AgD+/wEAAAAAAP//AgD9/wMA/v8BAP//AgD9/wMA/v8AAAEA//8BAP//AQD//wEA//8AAAEA/v8EAPz/AgD//wAAAQAAAP//AAABAP//AQD//wEAAAD//wEA//8BAAEA/f8DAP7/AQABAP3/AwD+/wIA/////wEAAAAAAAAAAAD//wIA/v8CAP////8CAP7/AgD//wAA//8CAP3/BAD9/wAAAgD9/wMA/v8BAP//AQAAAP//AQAAAP//AgD9/wMA/f8EAPz/AwD+/wEAAAAAAAAAAAD//wIA/f8EAP3/AAABAAAA//8CAP7/AQAAAP//AQAAAAAA//8BAP//AQAAAP//AQAAAP//AQAAAP//AgD9/wMA/v8BAP//AQAAAP//AQD//wIA/v8CAP3/BAD9/wEAAAD//wEAAQD9/wMA/f8CAAAA/v8DAP3/AgD//wAAAQD+/wIA/v8CAP7/AQAAAP//AgD+/wEAAAAAAP//AwD7/wUA/f8BAAEA/v8BAAEA/v8DAP3/AgD//wEA//8BAP//AQD//wEA//8CAP3/BAD7/wQA/////wIA/v8AAAIA/v8CAP3/BAD7/wUA/P8DAP3/AwD9/wMA/v8AAAIA/v8CAP7/AgD+/wIA//8AAAEA/v8CAP7/AgD//wAAAAD//wEAAAAAAAAA//8BAP7/BAD7/wUA/P8CAAAA//8BAP//AQAAAP//AgD9/wMA/v8BAAAA//8BAAAA//8CAP3/AwD+/wEA//8CAP3/AwD+/wAAAwD8/wIAAAD//wIA/////wIA/v8CAP7/AgD+/wEAAAAAAAAAAAAAAP//AgD+/wIA//8AAAAA//8CAP7/AgD+/wEA//8CAP3/AwD9/wMA/v8BAP7/AwD9/wMA/f8CAP//AQD+/wIA//8BAP//AQD+/wMA/v8BAAAA//8BAAAA//8CAP7/AQAAAP//AgD+/wIA/v8CAP//AAAAAAEA//8BAP//AAABAAAA//8BAP//AQD//wEA//8BAP//AQAAAP//AQD//wEAAAD//wIA/f8CAAAA//8BAAAA//8BAP//AAABAP//AQD//wAAAAAAAAEA/v8CAP//AQD//wAAAAABAP7/AwD9/wIAAAD+/wIA//8BAP//AgD9/wMA/f8DAP7/AgD+/wEAAAAAAAEA/v8CAP7/AgD//wAAAAAAAAAAAAAAAP//AgD/////AgD9/wQA/f8BAAAAAAAAAAEA/f8DAP////8DAP3/AQABAP7/AgD//wAAAQD+/wMA/f8CAP7/AQABAP7/AwD7/wYA+v8FAP3/AQABAP7/AgD+/wMA/f8CAP7/AwD+/wEA//8BAP//AQAAAP7/BQD5/wcA+v8FAPz/AwD+/wIA/v8BAAAA//8DAPv/BQD8/wMA/////wEAAAAAAAAAAAD//wIA/f8DAP7/AQAAAP//AQAAAP//AgD+/wIA/v8BAAEA/f8EAPz/AwD+/wEA//8CAP7/AQD//wEA//8CAP7/AQAAAP//AgD+/wEAAAAAAAAAAAAAAAAAAAD//wIA/f8EAPz/AwD+/wEA//8CAP7/AgD+/wEAAQD+/wEAAQD+/wIA/////wIA//8AAAAAAAAAAAAAAAD//wEAAAAAAP//AgD9/wMA/v8BAP//AQAAAP//AQD//wEA//8BAP//AQD//wEA//8BAP//AQAAAP7/AwD9/wMA/v8BAP7/AwD9/wMA/v8BAP//AAABAP//AQD//wAAAAABAP//AAAAAAAAAQD//wEA/v8CAAAA/v8EAPv/BAD9/wIAAAD+/wMA/P8DAP//AAAAAP//AQD//wIA/f8DAP3/AwD9/wMA/v8BAAAA//8BAAAA//8CAP3/AwD9/wQA+/8FAPv/BQD8/wMA/v8BAAAA//8BAP//AgD+/wEAAAD//wIA/v8BAAEA/f8DAP3/AgAAAP//AQD//wAAAQD//wEA//8BAP//AQD//wEA/v8DAP3/AgAAAP7/AwD9/wIAAAD//wEAAAD//wIA/f8DAP7/AgD9/wQA+/8FAPz/AgAAAP//AgD9/wIA//8BAP//AQD//wEA//8BAP//AQD//wIA/f8DAP3/AgD//wAAAQD+/wIA/v8BAAEA/v8CAP7/AgD+/wMA/P8DAP//AAABAP7/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA/v8CAP3/BAD8/wMA/v8BAAAAAAD//wEAAAAAAAAAAAD//wEAAAAAAAAA//8BAP//AgD+/wEA//8CAP3/AwD9/wMA/f8EAPv/BAD+/wAAAQD//wEA//8BAP//AAABAP//AQD//wEAAAD//wEA//8BAP//AgD9/wMA/v8AAAIA/f8DAP7/AAACAP3/AwD+/wEA//8BAP//AQAAAP//AQAAAP7/AwD9/wMA/v8AAAEA//8BAP//AAAAAAEA//8AAAEA/v8CAP//AAAAAAEA/v8DAPz/BAD9/wEAAQD+/wEAAQD9/wQA/P8DAP7/AQAAAAAAAAAAAAAAAAAAAAAAAQD+/wIA/////wIA/v8BAAAA//8BAP//AQD//wEA//8BAAAA/v8EAPz/AwD///7/BAD8/wMA/////wIA/v8CAP////8CAP7/AgD+/wIA/v8CAP////8CAP7/AwD9/wIA/v8CAP//AAABAP7/AwD9/wEAAQD+/wMA/f8CAP//AAAAAAEA/v8DAPz/BAD9/wIA/v8CAP7/AgD//wAAAAD//wIA/v8CAP7/AQAAAAAA//8CAP7/AgD+/wIA/v8CAP7/AwD8/wUA+v8GAPv/AwD//wAAAAAAAAAA//8DAPv/BQD9/wAAAgD9/wMA/v8BAP//AQAAAP//AgD9/wMA/v8BAAAA//8BAAAAAAAAAP//AQAAAAAAAAD//wEA//8CAP3/AwD+/wAAAgD+/wEAAAD//wIA/v8CAP7/AgD/////AwD8/wUA/P8CAP//AQD//wIA/f8DAP3/AwD+/wAAAQD+/wMA/f8DAP3/AgD//wAAAQD//wEA//8BAP7/AwD+/wEA//8AAAEA//8CAPz/BAD9/wIA//8AAAEA/v8DAPz/BAD9/wIA//8AAAEA/v8CAP7/AgD//wEA/f8EAPz/BAD+////AgD//wAAAQD//wAAAQD//wEA//8BAP7/AwD+/wEA'; + } + } catch (o) { + return void addTest('audiopreload', !1); + } + (r.setAttribute('preload', 'auto'), + (r.style.cssText = 'display:none'), + docElement.appendChild(r), + setTimeout(function () { + (r.addEventListener('loadeddata', A, !1), (e = setTimeout(A, t))); + }, 0)); + }), + Modernizr.addTest('canvasblending', function () { + if (Modernizr.canvas === !1) return !1; + var A = createElement('canvas').getContext('2d'); + try { + A.globalCompositeOperation = 'screen'; + } catch (e) {} + return 'screen' === A.globalCompositeOperation; + })); + var canvas = createElement('canvas'); + (Modernizr.addTest('todataurljpeg', function () { + return ( + !!Modernizr.canvas && + 0 === canvas.toDataURL('image/jpeg').indexOf('data:image/jpeg') + ); + }), + Modernizr.addTest('todataurlpng', function () { + return ( + !!Modernizr.canvas && + 0 === canvas.toDataURL('image/png').indexOf('data:image/png') + ); + }), + Modernizr.addTest('todataurlwebp', function () { + var A = !1; + try { + A = + !!Modernizr.canvas && + 0 === canvas.toDataURL('image/webp').indexOf('data:image/webp'); + } catch (e) {} + return A; + }), + Modernizr.addTest('canvaswinding', function () { + if (Modernizr.canvas === !1) return !1; + var A = createElement('canvas').getContext('2d'); + return ( + A.rect(0, 0, 10, 10), + A.rect(2, 2, 6, 6), + A.isPointInPath(5, 5, 'evenodd') === !1 + ); + }), + Modernizr.addTest('bgpositionshorthand', function () { + var A = createElement('a'), + e = A.style, + t = 'right 10px bottom 10px'; + return ( + (e.cssText = 'background-position: ' + t + ';'), + e.backgroundPosition === t + ); + }), + Modernizr.addTest('csscalc', function () { + var A = 'width:', + e = 'calc(10px);', + t = createElement('a'); + return ((t.style.cssText = A + prefixes.join(e + A)), !!t.style.length); + }), + Modernizr.addTest('cubicbezierrange', function () { + var A = createElement('a'); + return ( + (A.style.cssText = prefixes.join( + 'transition-timing-function:cubic-bezier(1,0,0,1.1); ' + )), + !!A.style.length + ); + }), + Modernizr.addTest('cssgradients', function () { + for ( + var A, + e = 'background-image:', + t = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', + r = '', + n = 0, + o = prefixes.length - 1; + o > n; + n++ + ) + ((A = 0 === n ? 'to ' : ''), + (r += + e + + prefixes[n] + + 'linear-gradient(' + + A + + 'left top, #9f9, white);')); + Modernizr._config.usePrefixes && (r += e + '-webkit-' + t); + var i = createElement('a'), + d = i.style; + return ( + (d.cssText = r), ('' + d.backgroundImage).indexOf('gradient') > -1 + ); + }), + Modernizr.addTest('multiplebgs', function () { + var A = createElement('a').style; + return ( + (A.cssText = + 'background:url(https://),url(https://),red url(https://)'), + /(url\s*\(.*?){3}/.test(A.background) + ); + }), + Modernizr.addTest('opacity', function () { + var A = createElement('a').style; + return ( + (A.cssText = prefixes.join('opacity:.55;')), /^0.55$/.test(A.opacity) + ); + }), + Modernizr.addTest('csspointerevents', function () { + var A = createElement('a').style; + return ((A.cssText = 'pointer-events:auto'), 'auto' === A.pointerEvents); + }), + Modernizr.addTest('csspositionsticky', function () { + var A = 'position:', + e = 'sticky', + t = createElement('a'), + r = t.style; + return ( + (r.cssText = A + prefixes.join(e + ';' + A).slice(0, -A.length)), + -1 !== r.position.indexOf(e) + ); + }), + Modernizr.addTest('cssremunit', function () { + var A = createElement('a').style; + try { + A.fontSize = '3rem'; + } catch (e) {} + return /rem/.test(A.fontSize); + }), + Modernizr.addTest('rgba', function () { + var A = createElement('a').style; + return ( + (A.cssText = 'background-color:rgba(150,255,150,.5)'), + ('' + A.backgroundColor).indexOf('rgba') > -1 + ); + }), + Modernizr.addTest('preserve3d', function () { + var A, + e, + t = window.CSS, + r = !1; + return t && t.supports && t.supports('(transform-style: preserve-3d)') + ? !0 + : ((A = createElement('a')), + (e = createElement('a')), + (A.style.cssText = + 'display: block; transform-style: preserve-3d; transform-origin: right; transform: rotateY(40deg);'), + (e.style.cssText = + 'display: block; width: 9px; height: 1px; background: #000; transform-origin: right; transform: rotateY(40deg);'), + A.appendChild(e), + docElement.appendChild(A), + (r = e.getBoundingClientRect()), + docElement.removeChild(A), + (r = r.width && r.width < 4)); + }), + Modernizr.addTest('dataset', function () { + var A = createElement('div'); + return ( + A.setAttribute('data-a-b', 'c'), !(!A.dataset || 'c' !== A.dataset.aB) + ); + }), + Modernizr.addTest('hidden', 'hidden' in createElement('a')), + Modernizr.addTest('outputelem', 'value' in createElement('output')), + Modernizr.addTest( + 'progressbar', + createElement('progress').max !== undefined + ), + Modernizr.addTest('meter', createElement('meter').max !== undefined), + Modernizr.addTest('ruby', function () { + function A(A, e) { + var t; + return ( + window.getComputedStyle + ? (t = document.defaultView + .getComputedStyle(A, null) + .getPropertyValue(e)) + : A.currentStyle && (t = A.currentStyle[e]), + t + ); + } + function e() { + (docElement.removeChild(t), (t = null), (r = null), (n = null)); + } + var t = createElement('ruby'), + r = createElement('rt'), + n = createElement('rp'), + o = 'display', + i = 'fontSize'; + return ( + t.appendChild(n), + t.appendChild(r), + docElement.appendChild(t), + 'none' == A(n, o) || + ('ruby' == A(t, o) && 'ruby-text' == A(r, o)) || + ('6pt' == A(n, i) && '6pt' == A(r, i)) + ? (e(), !0) + : (e(), !1) + ); + }), + Modernizr.addTest('template', 'content' in createElement('template')), + Modernizr.addTest('time', 'valueAsDate' in createElement('time')), + Modernizr.addTest( + 'texttrackapi', + 'function' == typeof createElement('video').addTextTrack + ), + Modernizr.addTest('track', 'kind' in createElement('track')), + Modernizr.addTest('unknownelements', function () { + var A = createElement('a'); + return ((A.innerHTML = ''), 1 === A.childNodes.length); + }), + Modernizr.addTest('capture', 'capture' in createElement('input')), + Modernizr.addTest('fileinput', function () { + if ( + navigator.userAgent.match( + /(Android (1.0|1.1|1.5|1.6|2.0|2.1))|(Windows Phone (OS 7|8.0))|(XBLWP)|(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|(Kindle\/(1.0|2.0|2.5|3.0))/ + ) + ) + return !1; + var A = createElement('input'); + return ((A.type = 'file'), !A.disabled); + }), + Modernizr.addTest('fileinputdirectory', function () { + var A = createElement('input'), + e = 'directory'; + if (((A.type = 'file'), e in A)) return !0; + for (var t = 0, r = domPrefixes.length; r > t; t++) + if (domPrefixes[t] + e in A) return !0; + return !1; + }), + Modernizr.addTest('formattribute', function () { + var A, + e = createElement('form'), + t = createElement('input'), + r = createElement('div'), + n = 'formtest' + new Date().getTime(), + o = !1; + e.id = n; + try { + t.setAttribute('form', n); + } catch (i) { + document.createAttribute && + ((A = document.createAttribute('form')), + (A.nodeValue = n), + t.setAttributeNode(A)); + } + return ( + r.appendChild(e), + r.appendChild(t), + docElement.appendChild(r), + (o = e.elements && 1 === e.elements.length && t.form == e), + r.parentNode.removeChild(r), + o + ); + }), + Modernizr.addTest( + 'placeholder', + 'placeholder' in createElement('input') && + 'placeholder' in createElement('textarea') + ), + Modernizr.addTest('sandbox', 'sandbox' in createElement('iframe')), + Modernizr.addTest('seamless', 'seamless' in createElement('iframe')), + Modernizr.addTest('srcdoc', 'srcdoc' in createElement('iframe')), + Modernizr.addAsyncTest(function () { + if (!Modernizr.canvas) return !1; + var A = new Image(), + e = createElement('canvas'), + t = e.getContext('2d'); + ((A.onload = function () { + addTest('apng', function () { + return 'undefined' == typeof e.getContext + ? !1 + : (t.drawImage(A, 0, 0), 0 === t.getImageData(0, 0, 1, 1).data[3]); + }); + }), + (A.src = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACGFjVEwAAAABAAAAAcMq2TYAAAANSURBVAiZY2BgYPgPAAEEAQB9ssjfAAAAGmZjVEwAAAAAAAAAAQAAAAEAAAAAAAAAAAD6A+gBAbNU+2sAAAARZmRBVAAAAAEImWNgYGBgAAAABQAB6MzFdgAAAABJRU5ErkJggg==')); + }), + Modernizr.addAsyncTest(function () { + var A, + e, + t, + r = createElement('img'), + n = 'sizes' in r; + !n && 'srcset' in r + ? ((e = + 'data:image/gif;base64,R0lGODlhAgABAPAAAP///wAAACH5BAAAAAAALAAAAAACAAEAAAICBAoAOw=='), + (A = + 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='), + (t = function () { + addTest('sizes', 2 == r.width); + }), + (r.onload = t), + (r.onerror = t), + r.setAttribute('sizes', '9px'), + (r.srcset = A + ' 1w,' + e + ' 8w'), + (r.src = A)) + : addTest('sizes', n); + }), + Modernizr.addTest('srcset', 'srcset' in createElement('img')), + Modernizr.addTest( + 'inputformaction', + !!('formAction' in createElement('input')), + { aliases: ['input-formaction'] } + ), + Modernizr.addTest( + 'inputformenctype', + !!('formEnctype' in createElement('input')), + { aliases: ['input-formenctype'] } + ), + Modernizr.addTest( + 'inputformmethod', + !!('formMethod' in createElement('input')) + ), + Modernizr.addTest( + 'inputformtarget', + !!('formtarget' in createElement('input')), + { aliases: ['input-formtarget'] } + ), + Modernizr.addTest('scriptasync', 'async' in createElement('script')), + Modernizr.addTest('scriptdefer', 'defer' in createElement('script')), + Modernizr.addTest('stylescoped', 'scoped' in createElement('style')), + Modernizr.addTest('inlinesvg', function () { + var A = createElement('div'); + return ( + (A.innerHTML = ''), + 'http://www.w3.org/2000/svg' == + ('undefined' != typeof SVGRect && + A.firstChild && + A.firstChild.namespaceURI) + ); + }), + Modernizr.addTest( + 'textareamaxlength', + !!('maxLength' in createElement('textarea')) + ), + Modernizr.addAsyncTest(function () { + function A(i) { + (n++, clearTimeout(e)); + var d = (i && 'playing' === i.type) || 0 !== o.currentTime; + return !d && r > n + ? void (e = setTimeout(A, t)) + : (o.removeEventListener('playing', A, !1), + addTest('videoautoplay', d), + void (o.parentNode && o.parentNode.removeChild(o))); + } + var e, + t = 200, + r = 5, + n = 0, + o = createElement('video'), + i = o.style; + if (!(Modernizr.video && 'autoplay' in o)) + return void addTest('videoautoplay', !1); + ((i.position = 'absolute'), (i.height = 0), (i.width = 0)); + try { + if (Modernizr.video.ogg) + o.src = + 'data:video/ogg;base64,T2dnUwACAAAAAAAAAABmnCATAAAAAHDEixYBKoB0aGVvcmEDAgEAAQABAAAQAAAQAAAAAAAFAAAAAQAAAAAAAAAAAGIAYE9nZ1MAAAAAAAAAAAAAZpwgEwEAAAACrA7TDlj///////////////+QgXRoZW9yYSsAAABYaXBoLk9yZyBsaWJ0aGVvcmEgMS4xIDIwMDkwODIyIChUaHVzbmVsZGEpAQAAABoAAABFTkNPREVSPWZmbXBlZzJ0aGVvcmEtMC4yOYJ0aGVvcmG+zSj3uc1rGLWpSUoQc5zmMYxSlKQhCDGMYhCEIQhAAAAAAAAAAAAAEW2uU2eSyPxWEvx4OVts5ir1aKtUKBMpJFoQ/nk5m41mUwl4slUpk4kkghkIfDwdjgajQYC8VioUCQRiIQh8PBwMhgLBQIg4FRba5TZ5LI/FYS/Hg5W2zmKvVoq1QoEykkWhD+eTmbjWZTCXiyVSmTiSSCGQh8PB2OBqNBgLxWKhQJBGIhCHw8HAyGAsFAiDgUCw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDAwPEhQUFQ0NDhESFRUUDg4PEhQVFRUOEBETFBUVFRARFBUVFRUVEhMUFRUVFRUUFRUVFRUVFRUVFRUVFRUVEAwLEBQZGxwNDQ4SFRwcGw4NEBQZHBwcDhATFhsdHRwRExkcHB4eHRQYGxwdHh4dGxwdHR4eHh4dHR0dHh4eHRALChAYKDM9DAwOExo6PDcODRAYKDlFOA4RFh0zV1A+EhYlOkRtZ00YIzdAUWhxXDFATldneXhlSFxfYnBkZ2MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTEhIVGRoaGhoSFBYaGhoaGhUWGRoaGhoaGRoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhESFh8kJCQkEhQYIiQkJCQWGCEkJCQkJB8iJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQREhgvY2NjYxIVGkJjY2NjGBo4Y2NjY2MvQmNjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRISEhUXGBkbEhIVFxgZGxwSFRcYGRscHRUXGBkbHB0dFxgZGxwdHR0YGRscHR0dHhkbHB0dHR4eGxwdHR0eHh4REREUFxocIBERFBcaHCAiERQXGhwgIiUUFxocICIlJRcaHCAiJSUlGhwgIiUlJSkcICIlJSUpKiAiJSUlKSoqEBAQFBgcICgQEBQYHCAoMBAUGBwgKDBAFBgcICgwQEAYHCAoMEBAQBwgKDBAQEBgICgwQEBAYIAoMEBAQGCAgAfF5cdH1e3Ow/L66wGmYnfIUbwdUTe3LMRbqON8B+5RJEvcGxkvrVUjTMrsXYhAnIwe0dTJfOYbWrDYyqUrz7dw/JO4hpmV2LsQQvkUeGq1BsZLx+cu5iV0e0eScJ91VIQYrmqfdVSK7GgjOU0oPaPOu5IcDK1mNvnD+K8LwS87f8Jx2mHtHnUkTGAurWZlNQa74ZLSFH9oF6FPGxzLsjQO5Qe0edcpttd7BXBSqMCL4k/4tFrHIPuEQ7m1/uIWkbDMWVoDdOSuRQ9286kvVUlQjzOE6VrNguN4oRXYGkgcnih7t13/9kxvLYKQezwLTrO44sVmMPgMqORo1E0sm1/9SludkcWHwfJwTSybR4LeAz6ugWVgRaY8mV/9SluQmtHrzsBtRF/wPY+X0JuYTs+ltgrXAmlk10xQHmTu9VSIAk1+vcvU4ml2oNzrNhEtQ3CysNP8UeR35wqpKUBdGdZMSjX4WVi8nJpdpHnbhzEIdx7mwf6W1FKAiucMXrWUWVjyRf23chNtR9mIzDoT/6ZLYailAjhFlZuvPtSeZ+2oREubDoWmT3TguY+JHPdRVSLKxfKH3vgNqJ/9emeEYikGXDFNzaLjvTeGAL61mogOoeG3y6oU4rW55ydoj0lUTSR/mmRhPmF86uwIfzp3FtiufQCmppaHDlGE0r2iTzXIw3zBq5hvaTldjG4CPb9wdxAme0SyedVKczJ9AtYbgPOzYKJvZZImsN7ecrxWZg5dR6ZLj/j4qpWsIA+vYwE+Tca9ounMIsrXMB4Stiib2SPQtZv+FVIpfEbzv8ncZoLBXc3YBqTG1HsskTTotZOYTG+oVUjLk6zhP8bg4RhMUNtfZdO7FdpBuXzhJ5Fh8IKlJG7wtD9ik8rWOJxy6iQ3NwzBpQ219mlyv+FLicYs2iJGSE0u2txzed++D61ZWCiHD/cZdQVCqkO2gJpdpNaObhnDfAPrT89RxdWFZ5hO3MseBSIlANppdZNIV/Rwe5eLTDvkfWKzFnH+QJ7m9QWV1KdwnuIwTNtZdJMoXBf74OhRnh2t+OTGL+AVUnIkyYY+QG7g9itHXyF3OIygG2s2kud679ZWKqSFa9n3IHD6MeLv1lZ0XyduRhiDRtrNnKoyiFVLcBm0ba5Yy3fQkDh4XsFE34isVpOzpa9nR8iCpS4HoxG2rJpnRhf3YboVa1PcRouh5LIJv/uQcPNd095ickTaiGBnWLKVWRc0OnYTSyex/n2FofEPnDG8y3PztHrzOLK1xo6RAml2k9owKajOC0Wr4D5x+3nA0UEhK2m198wuBHF3zlWWVKWLN1CHzLClUfuoYBcx4b1llpeBKmbayaR58njtE9onD66lUcsg0Spm2snsb+8HaJRn4dYcLbCuBuYwziB8/5U1C1DOOz2gZjSZtrLJk6vrLF3hwY4Io9xuT/ruUFRSBkNtUzTOWhjh26irLEPx4jPZL3Fo3QrReoGTTM21xYTT9oFdhTUIvjqTkfkvt0bzgVUjq/hOYY8j60IaO/0AzRBtqkTS6R5ellZd5uKdzzhb8BFlDdAcrwkE0rbXTOPB+7Y0FlZO96qFL4Ykg21StJs8qIW7h16H5hGiv8V2Cflau7QVDepTAHa6Lgt6feiEvJDM21StJsmOH/hynURrKxvUpQ8BH0JF7BiyG2qZpnL/7AOU66gt+reLEXY8pVOCQvSsBtqZTNM8bk9ohRcwD18o/WVkbvrceVKRb9I59IEKysjBeTMmmbA21xu/6iHadLRxuIzkLpi8wZYmmbbWi32RVAUjruxWlJ//iFxE38FI9hNKOoCdhwf5fDe4xZ81lgREhK2m1j78vW1CqkuMu/AjBNK210kzRUX/B+69cMMUG5bYrIeZxVSEZISmkzbXOi9yxwIfPgdsov7R71xuJ7rFcACjG/9PzApqFq7wEgzNJm2suWESPuwrQvejj7cbnQxMkxpm21lUYJL0fKmogPPqywn7e3FvB/FCNxPJ85iVUkCE9/tLKx31G4CgNtWTTPFhMvlu8G4/TrgaZttTChljfNJGgOT2X6EqpETy2tYd9cCBI4lIXJ1/3uVUllZEJz4baqGF64yxaZ+zPLYwde8Uqn1oKANtUrSaTOPHkhvuQP3bBlEJ/LFe4pqQOHUI8T8q7AXx3fLVBgSCVpMba55YxN3rv8U1Dv51bAPSOLlZWebkL8vSMGI21lJmmeVxPRwFlZF1CpqCN8uLwymaZyjbXHCRytogPN3o/n74CNykfT+qqRv5AQlHcRxYrC5KvGmbbUwmZY/29BvF6C1/93x4WVglXDLFpmbapmF89HKTogRwqqSlGbu+oiAkcWFbklC6Zhf+NtTLFpn8oWz+HsNRVSgIxZWON+yVyJlE5tq/+GWLTMutYX9ekTySEQPLVNQQ3OfycwJBM0zNtZcse7CvcKI0V/zh16Dr9OSA21MpmmcrHC+6pTAPHPwoit3LHHqs7jhFNRD6W8+EBGoSEoaZttTCZljfduH/fFisn+dRBGAZYtMzbVMwvul/T/crK1NQh8gN0SRRa9cOux6clC0/mDLFpmbarmF8/e6CopeOLCNW6S/IUUg3jJIYiAcDoMcGeRbOvuTPjXR/tyo79LK3kqqkbxkkMRAOB0GODPItnX3Jnxro/25Ud+llbyVVSN4ySGIgHA6DHBnkWzr7kz410f7cqO/Syt5KqpFVJwn6gBEvBM0zNtZcpGOEPiysW8vvRd2R0f7gtjhqUvXL+gWVwHm4XJDBiMpmmZtrLfPwd/IugP5+fKVSysH1EXreFAcEhelGmbbUmZY4Xdo1vQWVnK19P4RuEnbf0gQnR+lDCZlivNM22t1ESmopPIgfT0duOfQrsjgG4tPxli0zJmF5trdL1JDUIUT1ZXSqQDeR4B8mX3TrRro/2McGeUvLtwo6jIEKMkCUXWsLyZROd9P/rFYNtXPBli0z398iVUlVKAjFlY437JXImUTm2r/4ZYtMy61hf16RPJIU9nZ1MABAwAAAAAAAAAZpwgEwIAAABhp658BScAAAAAAADnUFBQXIDGXLhwtttNHDhw5OcpQRMETBEwRPduylKVB0HRdF0A'; + else { + if (!Modernizr.video.h264) return void addTest('videoautoplay', !1); + o.src = + 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAs1tZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0OCByMjYwMSBhMGNkN2QzIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNSAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTEgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEwIHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAD2WIhAA3//728P4FNjuZQQAAAu5tb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAAZAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACGHRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAZAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAgAAAAIAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAGQAAAAAAAEAAAAAAZBtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAACgAAAAEAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAE7bWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAAA+3N0YmwAAACXc3RzZAAAAAAAAAABAAAAh2F2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAgACAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAxYXZjQwFkAAr/4QAYZ2QACqzZX4iIhAAAAwAEAAADAFA8SJZYAQAGaOvjyyLAAAAAGHN0dHMAAAAAAAAAAQAAAAEAAAQAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAABRzdHN6AAAAAAAAAsUAAAABAAAAFHN0Y28AAAAAAAAAAQAAADAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjU2LjQwLjEwMQ=='; + } + } catch (d) { + return void addTest('videoautoplay', !1); + } + (o.setAttribute('autoplay', ''), + (o.style.cssText = 'display:none'), + docElement.appendChild(o), + setTimeout(function () { + (o.addEventListener('playing', A, !1), (e = setTimeout(A, t))); + }, 0)); + }), + Modernizr.addTest('videoloop', 'loop' in createElement('video')), + Modernizr.addTest('videopreload', 'preload' in createElement('video')), + Modernizr.addAsyncTest(function () { + if (((Modernizr.webglextensions = !1), Modernizr.webgl)) { + var A, e, t; + try { + ((A = createElement('canvas')), + (e = A.getContext('webgl') || A.getContext('experimental-webgl')), + (t = e.getSupportedExtensions())); + } catch (r) { + return; + } + e !== undefined && (Modernizr.webglextensions = new Boolean(!0)); + for (var n = -1, o = t.length; ++n < o;) + Modernizr.webglextensions[t[n]] = !0; + A = undefined; + } + }), + Modernizr.addAsyncTest(function () { + var A, + e, + t = function (A) { + docElement.contains(A) || docElement.appendChild(A); + }, + r = function (A) { + A.fake && A.parentNode && A.parentNode.removeChild(A); + }, + n = function (A, e) { + var t = !!A; + if ( + (t && ((t = new Boolean(t)), (t.blocked = 'blocked' === A)), + addTest('flash', function () { + return t; + }), + e && s.contains(e)) + ) { + for (; e.parentNode !== s;) e = e.parentNode; + s.removeChild(e); + } + }; + try { + e = + 'ActiveXObject' in window && + 'Pan' in new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash'); + } catch (o) {} + if ( + ((A = !( + ('plugins' in navigator && 'Shockwave Flash' in navigator.plugins) || + e + )), + A || isSVG) + ) + n(!1); + else { + var i, + d, + a = createElement('embed'), + s = getBody(); + if ( + ((a.type = 'application/x-shockwave-flash'), + s.appendChild(a), + !('Pan' in a || e)) + ) + return (t(s), n('blocked', a), void r(s)); + ((i = function () { + return ( + t(s), + docElement.contains(s) + ? (docElement.contains(a) + ? ((d = a.style.cssText), + '' !== d ? n('blocked', a) : n(!0, a)) + : n('blocked'), + void r(s)) + : ((s = document.body || s), + (a = createElement('embed')), + (a.type = 'application/x-shockwave-flash'), + s.appendChild(a), + setTimeout(i, 1e3)) + ); + }), + setTimeout(i, 10)); + } + })); + var mq = (function () { + var A = window.matchMedia || window.msMatchMedia; + return A + ? function (e) { + var t = A(e); + return (t && t.matches) || !1; + } + : function (A) { + var e = !1; + return ( + injectElementWithStyles( + '@media ' + A + ' { #modernizr { position: absolute; } }', + function (A) { + e = + 'absolute' == + (window.getComputedStyle + ? window.getComputedStyle(A, null) + : A.currentStyle + ).position; + } + ), + e + ); + }; + })(); + ((ModernizrProto.mq = mq), Modernizr.addTest('mediaqueries', mq('only all'))); + var testStyles = (ModernizrProto.testStyles = injectElementWithStyles); + (Modernizr.addTest('hiddenscroll', function () { + return testStyles( + '#modernizr {width:100px;height:100px;overflow:scroll}', + function (A) { + return A.offsetWidth === A.clientWidth; + } + ); + }), + Modernizr.addTest('mathml', function () { + var A; + return ( + testStyles( + '#modernizr{position:absolute;display:inline-block}', + function (e) { + ((e.innerHTML += + 'xxyy'), + (A = e.offsetHeight > e.offsetWidth)); + } + ), + A + ); + }), + Modernizr.addTest('touchevents', function () { + var A; + if ( + 'ontouchstart' in window || + (window.DocumentTouch && document instanceof DocumentTouch) + ) + A = !0; + else { + var e = [ + '@media (', + prefixes.join('touch-enabled),('), + 'heartz', + ')', + '{#modernizr{top:9px;position:absolute}}' + ].join(''); + testStyles(e, function (e) { + A = 9 === e.offsetTop; + }); + } + return A; + }), + Modernizr.addTest('unicoderange', function () { + return Modernizr.testStyles( + '@font-face{font-family:"unicodeRange";src:local("Arial");unicode-range:U+0020,U+002E}#modernizr span{font-size:20px;display:inline-block;font-family:"unicodeRange",monospace}#modernizr .mono{font-family:monospace}', + function (A) { + for (var e = ['.', '.', 'm', 'm'], t = 0; t < e.length; t++) { + var r = createElement('span'); + ((r.innerHTML = e[t]), + (r.className = t % 2 ? 'mono' : ''), + A.appendChild(r), + (e[t] = r.clientWidth)); + } + return e[0] !== e[1] && e[2] === e[3]; + } + ); + }), + Modernizr.addTest('unicode', function () { + var A, + e = createElement('span'), + t = createElement('span'); + return ( + testStyles( + '#modernizr{font-family:Arial,sans;font-size:300em;}', + function (r) { + ((e.innerHTML = isSVG ? '妇' : 'ᝣ'), + (t.innerHTML = isSVG ? '☆' : '☆'), + r.appendChild(e), + r.appendChild(t), + (A = 'offsetWidth' in e && e.offsetWidth !== t.offsetWidth)); + } + ), + A + ); + }), + Modernizr.addTest('checked', function () { + return testStyles( + '#modernizr {position:absolute} #modernizr input {margin-left:10px} #modernizr :checked {margin-left:20px;display:block}', + function (A) { + var e = createElement('input'); + return ( + e.setAttribute('type', 'checkbox'), + e.setAttribute('checked', 'checked'), + A.appendChild(e), + 20 === e.offsetLeft + ); + } + ); + }), + testStyles( + '#modernizr{display: table; direction: ltr}#modernizr div{display: table-cell; padding: 10px}', + function (A) { + var e, + t = A.childNodes; + ((e = t[0].offsetLeft < t[1].offsetLeft), + Modernizr.addTest('displaytable', e, { aliases: ['display-table'] })); + }, + 2 + )); + var blacklist = (function () { + var A = navigator.userAgent, + e = A.match(/w(eb)?osbrowser/gi), + t = + A.match(/windows phone/gi) && + A.match(/iemobile\/([0-9])+/gi) && + parseFloat(RegExp.$1) >= 9; + return e || t; + })(); + (blacklist + ? Modernizr.addTest('fontface', !1) + : testStyles( + '@font-face {font-family:"font";src:url("https://")}', + function (A, e) { + var t = document.getElementById('smodernizr'), + r = t.sheet || t.styleSheet, + n = r + ? r.cssRules && r.cssRules[0] + ? r.cssRules[0].cssText + : r.cssText || '' + : '', + o = /src/i.test(n) && 0 === n.indexOf(e.split(' ')[0]); + Modernizr.addTest('fontface', o); + } + ), + testStyles( + '#modernizr{font:0/0 a}#modernizr:after{content:":)";visibility:hidden;font:7px/1 a}', + function (A) { + Modernizr.addTest('generatedcontent', A.offsetHeight >= 6); + } + ), + Modernizr.addTest('cssinvalid', function () { + return testStyles( + '#modernizr input{height:0;border:0;padding:0;margin:0;width:10px} #modernizr input:invalid{width:50px}', + function (A) { + var e = createElement('input'); + return ((e.required = !0), A.appendChild(e), e.clientWidth > 10); + } + ); + }), + testStyles( + '#modernizr div {width:100px} #modernizr :last-child{width:200px;display:block}', + function (A) { + Modernizr.addTest( + 'lastchild', + A.lastChild.offsetWidth > A.firstChild.offsetWidth + ); + }, + 2 + ), + testStyles( + '#modernizr div {width:1px} #modernizr div:nth-child(2n) {width:2px;}', + function (A) { + for (var e = A.getElementsByTagName('div'), t = !0, r = 0; 5 > r; r++) + t = t && e[r].offsetWidth === (r % 2) + 1; + Modernizr.addTest('nthchild', t); + }, + 5 + ), + testStyles( + '#modernizr{overflow: scroll; width: 40px; height: 40px; }#' + + prefixes + .join('scrollbar{width:10px} #modernizr::') + .split('#') + .slice(1) + .join('#') + + 'scrollbar{width:10px}', + function (A) { + Modernizr.addTest( + 'cssscrollbar', + 'scrollWidth' in A && 30 == A.scrollWidth + ); + } + ), + Modernizr.addTest('siblinggeneral', function () { + return testStyles( + '#modernizr div {width:100px} #modernizr div ~ div {width:200px;display:block}', + function (A) { + return 200 == A.lastChild.offsetWidth; + }, + 2 + ); + }), + testStyles( + '#modernizr{position: absolute; top: -10em; visibility:hidden; font: normal 10px arial;}#subpixel{float: left; font-size: 33.3333%;}', + function (A) { + var e = A.firstChild; + ((e.innerHTML = 'This is a text written in Arial'), + Modernizr.addTest( + 'subpixelfont', + window.getComputedStyle + ? '44px' !== + window.getComputedStyle(e, null).getPropertyValue('width') + : !1 + )); + }, + 1, + ['subpixel'] + ), + Modernizr.addTest('cssvalid', function () { + return testStyles( + '#modernizr input{height:0;border:0;padding:0;margin:0;width:10px} #modernizr input:valid{width:50px}', + function (A) { + var e = createElement('input'); + return (A.appendChild(e), e.clientWidth > 10); + } + ); + }), + Modernizr.addTest('details', function () { + var A, + e = createElement('details'); + return 'open' in e + ? (testStyles('#modernizr details{display:block}', function (t) { + (t.appendChild(e), + (e.innerHTML = 'ab'), + (A = e.offsetHeight), + (e.open = !0), + (A = A != e.offsetHeight)); + }), + A) + : !1; + }), + Modernizr.addTest('oninput', function () { + var A, + e = createElement('input'); + if ( + (e.setAttribute('oninput', 'return'), + hasEvent('oninput', docElement) || 'function' == typeof e.oninput) + ) + return !0; + try { + var t = document.createEvent('KeyboardEvent'); + A = !1; + var r = function (e) { + ((A = !0), e.preventDefault(), e.stopPropagation()); + }; + (t.initKeyEvent( + 'keypress', + !0, + !0, + window, + !1, + !1, + !1, + !1, + 0, + 'e'.charCodeAt(0) + ), + docElement.appendChild(e), + e.addEventListener('input', r, !1), + e.focus(), + e.dispatchEvent(t), + e.removeEventListener('input', r, !1), + docElement.removeChild(e)); + } catch (n) { + A = !1; + } + return A; + }), + Modernizr.addTest('formvalidation', function () { + var A = createElement('form'); + if (!('checkValidity' in A && 'addEventListener' in A)) return !1; + if ('reportValidity' in A) return !0; + var e, + t = !1; + return ( + (Modernizr.formvalidationapi = !0), + A.addEventListener( + 'submit', + function (A) { + ((!window.opera || window.operamini) && A.preventDefault(), + A.stopPropagation()); + }, + !1 + ), + (A.innerHTML = + ''), + testStyles( + '#modernizr form{position:absolute;top:-99999em}', + function (r) { + (r.appendChild(A), + (e = A.getElementsByTagName('input')[0]), + e.addEventListener( + 'invalid', + function (A) { + ((t = !0), A.preventDefault(), A.stopPropagation()); + }, + !1 + ), + (Modernizr.formvalidationmessage = !!e.validationMessage), + A.getElementsByTagName('button')[0].click()); + } + ), + t + ); + })); + var inputElem = createElement('input'), + inputattrs = + 'autocomplete autofocus list placeholder max min multiple pattern required step'.split( + ' ' + ), + attrs = {}; + ((Modernizr.input = (function (A) { + for (var e = 0, t = A.length; t > e; e++) + attrs[A[e]] = !!(A[e] in inputElem); + return ( + attrs.list && + (attrs.list = !( + !createElement('datalist') || !window.HTMLDataListElement + )), + attrs + ); + })(inputattrs)), + Modernizr.addTest('datalistelem', Modernizr.input.list)); + var inputtypes = + 'search tel url email datetime date month week time datetime-local number range color'.split( + ' ' + ), + inputs = {}; + ((Modernizr.inputtypes = (function (A) { + for (var e, t, r, n = A.length, o = '1)', i = 0; n > i; i++) + (inputElem.setAttribute('type', (e = A[i])), + (r = 'text' !== inputElem.type && 'style' in inputElem), + r && + ((inputElem.value = o), + (inputElem.style.cssText = 'position:absolute;visibility:hidden;'), + /^range$/.test(e) && inputElem.style.WebkitAppearance !== undefined + ? (docElement.appendChild(inputElem), + (t = document.defaultView), + (r = + t.getComputedStyle && + 'textfield' !== + t.getComputedStyle(inputElem, null).WebkitAppearance && + 0 !== inputElem.offsetHeight), + docElement.removeChild(inputElem)) + : /^(search|tel)$/.test(e) || + (r = /^(url|email)$/.test(e) + ? inputElem.checkValidity && inputElem.checkValidity() === !1 + : inputElem.value != o)), + (inputs[A[i]] = !!r)); + return inputs; + })(inputtypes)), + Modernizr.addTest('localizednumber', function () { + if (!Modernizr.inputtypes.number) return !1; + if (!Modernizr.formvalidation) return !1; + var A, + e = createElement('div'), + t = getBody(), + r = (function () { + return docElement.insertBefore( + t, + docElement.firstElementChild || docElement.firstChild + ); + })(); + e.innerHTML = ''; + var n = e.childNodes[0]; + (r.appendChild(e), n.focus()); + try { + (document.execCommand('SelectAll', !1), + document.execCommand('InsertText', !1, '1,1')); + } catch (o) {} + return ( + (A = + 'number' === n.type && 1.1 === n.valueAsNumber && n.checkValidity()), + r.removeChild(e), + t.fake && r.parentNode.removeChild(r), + A + ); + })); + var modElem = { elem: createElement('modernizr') }; + (Modernizr._q.push(function () { + delete modElem.elem; + }), + Modernizr.addTest('csschunit', function () { + var A, + e = modElem.elem.style; + try { + ((e.fontSize = '3ch'), (A = -1 !== e.fontSize.indexOf('ch'))); + } catch (t) { + A = !1; + } + return A; + }), + Modernizr.addTest('cssexunit', function () { + var A, + e = modElem.elem.style; + try { + ((e.fontSize = '3ex'), (A = -1 !== e.fontSize.indexOf('ex'))); + } catch (t) { + A = !1; + } + return A; + }), + Modernizr.addTest('hsla', function () { + var A = createElement('a').style; + return ( + (A.cssText = 'background-color:hsla(120,40%,100%,.5)'), + contains(A.backgroundColor, 'rgba') || + contains(A.backgroundColor, 'hsla') + ); + }), + testStyles('#modernizr { height: 50vh; }', function (A) { + var e = parseInt(window.innerHeight / 2, 10), + t = parseInt(computedStyle(A, null, 'height'), 10); + Modernizr.addTest('cssvhunit', t == e); + }), + testStyles('#modernizr { width: 50vw; }', function (A) { + var e = parseInt(window.innerWidth / 2, 10), + t = parseInt(computedStyle(A, null, 'width'), 10); + Modernizr.addTest('cssvwunit', t == e); + }), + Modernizr.addTest('bdi', function () { + var A = createElement('div'), + e = createElement('bdi'); + ((e.innerHTML = 'إ'), A.appendChild(e), docElement.appendChild(A)); + var t = 'rtl' === computedStyle(e, null, 'direction'); + return (docElement.removeChild(A), t); + }), + testStyles( + '#modernizr1{width: 50vmax}#modernizr2{width:50px;height:50px;overflow:scroll}#modernizr3{position:fixed;top:0;left:0;bottom:0;right:0}', + function (A) { + var e = A.childNodes[2], + t = A.childNodes[1], + r = A.childNodes[0], + n = parseInt((t.offsetWidth - t.clientWidth) / 2, 10), + o = r.clientWidth / 100, + i = r.clientHeight / 100, + d = parseInt(50 * Math.max(o, i), 10), + a = parseInt(computedStyle(e, null, 'width'), 10); + Modernizr.addTest( + 'cssvmaxunit', + roundedEquals(d, a) || roundedEquals(d, a - n) + ); + }, + 3 + ), + testStyles( + '#modernizr1{width: 50vm;width:50vmin}#modernizr2{width:50px;height:50px;overflow:scroll}#modernizr3{position:fixed;top:0;left:0;bottom:0;right:0}', + function (A) { + var e = A.childNodes[2], + t = A.childNodes[1], + r = A.childNodes[0], + n = parseInt((t.offsetWidth - t.clientWidth) / 2, 10), + o = r.clientWidth / 100, + i = r.clientHeight / 100, + d = parseInt(50 * Math.min(o, i), 10), + a = parseInt(computedStyle(e, null, 'width'), 10); + Modernizr.addTest( + 'cssvminunit', + roundedEquals(d, a) || roundedEquals(d, a - n) + ); + }, + 3 + )); + var testXhrType = function (A) { + if ('undefined' == typeof XMLHttpRequest) return !1; + var e = new XMLHttpRequest(); + e.open('get', '/', !0); + try { + e.responseType = A; + } catch (t) { + return !1; + } + return 'response' in e && e.responseType == A; + }; + (Modernizr.addTest('xhrresponsetypearraybuffer', testXhrType('arraybuffer')), + Modernizr.addTest('xhrresponsetypedocument', testXhrType('document')), + Modernizr.addTest('xhrresponsetypeblob', testXhrType('blob')), + Modernizr.addTest('xhrresponsetypejson', testXhrType('json')), + Modernizr.addTest('xhrresponsetypetext', testXhrType('text'))); + var toStringFn = {}.toString; + (Modernizr.addTest('svgclippaths', function () { + return ( + !!document.createElementNS && + /SVGClipPath/.test( + toStringFn.call( + document.createElementNS('http://www.w3.org/2000/svg', 'clipPath') + ) + ) + ); + }), + Modernizr.addTest('svgforeignobject', function () { + return ( + !!document.createElementNS && + /SVGForeignObject/.test( + toStringFn.call( + document.createElementNS( + 'http://www.w3.org/2000/svg', + 'foreignObject' + ) + ) + ) + ); + }), + Modernizr.addTest('smil', function () { + return ( + !!document.createElementNS && + /SVGAnimate/.test( + toStringFn.call( + document.createElementNS('http://www.w3.org/2000/svg', 'animate') + ) + ) + ); + })); + var mStyle = { style: modElem.elem.style }; + Modernizr._q.unshift(function () { + delete mStyle.style; + }); + var testProp = (ModernizrProto.testProp = function (A, e, t) { + return testProps([A], undefined, e, t); + }); + (Modernizr.addTest('textshadow', testProp('textShadow', '1px 1px')), + (ModernizrProto.testAllProps = testPropsAll)); + var prefixed = (ModernizrProto.prefixed = function (A, e, t) { + return 0 === A.indexOf('@') + ? atRule(A) + : (-1 != A.indexOf('-') && (A = cssToDOM(A)), + e ? testPropsAll(A, e, t) : testPropsAll(A, 'pfx')); + }), + prefixedCSS = (ModernizrProto.prefixedCSS = function (A) { + var e = prefixed(A); + return e && domToCSS(e); + }); + Modernizr.addTest('batteryapi', !!prefixed('battery', navigator), { + aliases: ['battery-api'] + }); + var crypto = prefixed('crypto', window); + (Modernizr.addTest('crypto', !!prefixed('subtle', crypto)), + Modernizr.addTest('dart', !!prefixed('startDart', navigator)), + Modernizr.addTest( + 'fullscreen', + !( + !prefixed('exitFullscreen', document, !1) && + !prefixed('cancelFullScreen', document, !1) + ) + ), + Modernizr.addTest('gamepads', !!prefixed('getGamepads', navigator)), + Modernizr.addAsyncTest(function () { + var A; + try { + A = prefixed('indexedDB', window); + } catch (e) {} + if (A) { + var t = 'modernizr-' + Math.random(), + r = A.open(t); + ((r.onerror = function () { + r.error && 'InvalidStateError' === r.error.name + ? addTest('indexeddb', !1) + : (addTest('indexeddb', !0), detectDeleteDatabase(A, t)); + }), + (r.onsuccess = function () { + (addTest('indexeddb', !0), detectDeleteDatabase(A, t)); + })); + } else addTest('indexeddb', !1); + }), + Modernizr.addAsyncTest(function () { + var A, + e, + t, + r, + n = 'detect-blob-support', + o = !1; + try { + A = prefixed('indexedDB', window); + } catch (i) {} + if (!Modernizr.indexeddb || !Modernizr.indexeddb.deletedatabase) + return !1; + try { + A.deleteDatabase(n).onsuccess = function () { + ((e = A.open(n, 1)), + (e.onupgradeneeded = function () { + e.result.createObjectStore('store'); + }), + (e.onsuccess = function () { + t = e.result; + try { + ((r = t + .transaction('store', 'readwrite') + .objectStore('store') + .put(new Blob(), 'key')), + (r.onsuccess = function () { + o = !0; + }), + (r.onerror = function () { + o = !1; + })); + } catch (i) { + o = !1; + } finally { + (addTest('indexeddbblob', o), t.close(), A.deleteDatabase(n)); + } + })); + }; + } catch (i) { + addTest('indexeddbblob', !1); + } + }), + Modernizr.addTest('intl', !!prefixed('Intl', window)), + Modernizr.addTest('pagevisibility', !!prefixed('hidden', document, !1)), + Modernizr.addTest('performance', !!prefixed('performance', window)), + Modernizr.addTest('pointerlock', !!prefixed('exitPointerLock', document)), + Modernizr.addTest('quotamanagement', function () { + var A = prefixed('temporaryStorage', navigator), + e = prefixed('persistentStorage', navigator); + return !(!A || !e); + }), + Modernizr.addTest( + 'requestanimationframe', + !!prefixed('requestAnimationFrame', window), + { aliases: ['raf'] } + ), + Modernizr.addTest('vibrate', !!prefixed('vibrate', navigator)), + Modernizr.addTest('webintents', !!prefixed('startActivity', navigator)), + Modernizr.addTest('lowbattery', function () { + var A = 0.2, + e = prefixed('battery', navigator); + return !!(e && !e.charging && e.level <= A); + })); + var crypto = prefixed('crypto', window), + supportsGetRandomValues; + if (crypto && 'getRandomValues' in crypto && 'Uint32Array' in window) { + var array = new Uint32Array(10), + values = crypto.getRandomValues(array); + supportsGetRandomValues = values && is(values[0], 'number'); + } + (Modernizr.addTest('getrandomvalues', !!supportsGetRandomValues), + Modernizr.addTest( + 'backgroundblendmode', + prefixed('backgroundBlendMode', 'text') + ), + Modernizr.addTest('objectfit', !!prefixed('objectFit'), { + aliases: ['object-fit'] + }), + Modernizr.addTest('regions', function () { + if (isSVG) return !1; + var A = prefixed('flowFrom'), + e = prefixed('flowInto'), + t = !1; + if (!A || !e) return t; + var r = createElement('iframe'), + n = createElement('div'), + o = createElement('div'), + i = createElement('div'), + d = 'modernizr_flow_for_regions_check'; + ((o.innerText = 'M'), + (n.style.cssText = 'top: 150px; left: 150px; padding: 0px;'), + (i.style.cssText = 'width: 50px; height: 50px; padding: 42px;'), + (i.style[A] = d), + n.appendChild(o), + n.appendChild(i), + docElement.appendChild(n)); + var a, + s, + l = o.getBoundingClientRect(); + return ( + (o.style[e] = d), + (a = o.getBoundingClientRect()), + (s = parseInt(a.left - l.left, 10)), + docElement.removeChild(n), + 42 == s + ? (t = !0) + : (docElement.appendChild(r), + (l = r.getBoundingClientRect()), + (r.style[e] = d), + (a = r.getBoundingClientRect()), + l.height > 0 && + l.height !== a.height && + 0 === a.height && + (t = !0)), + (o = i = n = r = undefined), + t + ); + }), + Modernizr.addTest('wrapflow', function () { + var A = prefixed('wrapFlow'); + if (!A || isSVG) return !1; + var e = A.replace(/([A-Z])/g, function (A, e) { + return '-' + e.toLowerCase(); + }).replace(/^ms-/, '-ms-'), + t = createElement('div'), + r = createElement('div'), + n = createElement('span'); + ((r.style.cssText = + 'position: absolute; left: 50px; width: 100px; height: 20px;' + + e + + ':end;'), + (n.innerText = 'X'), + t.appendChild(r), + t.appendChild(n), + docElement.appendChild(t)); + var o = n.offsetLeft; + return (docElement.removeChild(t), (r = n = t = undefined), 150 == o); + }), + Modernizr.addTest('filesystem', !!prefixed('requestFileSystem', window)), + Modernizr.addTest( + 'requestautocomplete', + !!prefixed('requestAutocomplete', createElement('form')) + ), + Modernizr.addTest( + 'speechrecognition', + !!prefixed('SpeechRecognition', window) + )); + var url = prefixed('URL', window, !1); + ((url = url && window[url]), + Modernizr.addTest( + 'bloburls', + url && 'revokeObjectURL' in url && 'createObjectURL' in url + ), + Modernizr.addAsyncTest(function () { + function A() { + (addTest('transferables', !1), e()); + } + function e() { + (d && URL.revokeObjectURL(d), a && a.terminate(), n && clearTimeout(n)); + } + var t = !!( + Modernizr.blobconstructor && + Modernizr.bloburls && + Modernizr.webworkers && + Modernizr.typedarrays + ); + if (!t) return addTest('transferables', !1); + try { + var r, + n, + o = 'var hello = "world"', + i = new Blob([o], { type: 'text/javascript' }), + d = URL.createObjectURL(i), + a = new Worker(d); + ((a.onerror = A), + (n = setTimeout(A, 200)), + (r = new ArrayBuffer(1)), + a.postMessage(r, [r]), + addTest('transferables', 0 === r.byteLength), + e()); + } catch (s) { + A(); + } + }), + Modernizr.addTest('getusermedia', !!prefixed('getUserMedia', navigator)), + Modernizr.addTest( + 'peerconnection', + !!prefixed('RTCPeerConnection', window) + ), + Modernizr.addTest('matchmedia', !!prefixed('matchMedia', window)), + (ModernizrProto.testAllProps = testAllProps), + Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', !0)), + Modernizr.addTest('csspseudoanimations', function () { + var A = !1; + if (!Modernizr.cssanimations || !window.getComputedStyle) return A; + var e = [ + '@', + Modernizr._prefixes + .join('keyframes csspseudoanimations { from { font-size: 10px; } }@') + .replace(/\@$/, ''), + '#modernizr:before { content:" "; font-size:5px;', + Modernizr._prefixes.join('animation:csspseudoanimations 1ms infinite;'), + '}' + ].join(''); + return ( + Modernizr.testStyles(e, function (e) { + A = + '10px' === + window.getComputedStyle(e, ':before').getPropertyValue('font-size'); + }), + A + ); + }), + Modernizr.addTest('appearance', testAllProps('appearance')), + Modernizr.addTest('backdropfilter', testAllProps('backdropFilter')), + Modernizr.addTest('backgroundcliptext', function () { + return testAllProps('backgroundClip', 'text'); + }), + Modernizr.addTest('bgpositionxy', function () { + return ( + testAllProps('backgroundPositionX', '3px', !0) && + testAllProps('backgroundPositionY', '5px', !0) + ); + }), + Modernizr.addTest( + 'bgrepeatround', + testAllProps('backgroundRepeat', 'round') + ), + Modernizr.addTest( + 'bgrepeatspace', + testAllProps('backgroundRepeat', 'space') + ), + Modernizr.addTest( + 'backgroundsize', + testAllProps('backgroundSize', '100%', !0) + ), + Modernizr.addTest('bgsizecover', testAllProps('backgroundSize', 'cover')), + Modernizr.addTest( + 'borderimage', + testAllProps('borderImage', 'url() 1', !0) + ), + Modernizr.addTest('borderradius', testAllProps('borderRadius', '0px', !0)), + Modernizr.addTest('boxshadow', testAllProps('boxShadow', '1px 1px', !0)), + Modernizr.addTest( + 'boxsizing', + testAllProps('boxSizing', 'border-box', !0) && + (document.documentMode === undefined || document.documentMode > 7) + ), + (function () { + Modernizr.addTest('csscolumns', function () { + var A = !1, + e = testAllProps('columnCount'); + try { + ((A = !!e), A && (A = new Boolean(A))); + } catch (t) {} + return A; + }); + for ( + var A, + e, + t = [ + 'Width', + 'Span', + 'Fill', + 'Gap', + 'Rule', + 'RuleColor', + 'RuleStyle', + 'RuleWidth', + 'BreakBefore', + 'BreakAfter', + 'BreakInside' + ], + r = 0; + r < t.length; + r++ + ) + ((A = t[r].toLowerCase()), + (e = testAllProps('column' + t[r])), + ('breakbefore' === A || 'breakafter' === A || 'breakinside' == A) && + (e = e || testAllProps(t[r])), + Modernizr.addTest('csscolumns.' + A, e)); + })(), + Modernizr.addTest('ellipsis', testAllProps('textOverflow', 'ellipsis')), + Modernizr.addTest('cssfilters', function () { + if (Modernizr.supports) return testAllProps('filter', 'blur(2px)'); + var A = createElement('a'); + return ( + (A.style.cssText = prefixes.join('filter:blur(2px); ')), + !!A.style.length && + (document.documentMode === undefined || document.documentMode > 9) + ); + }), + Modernizr.addTest('flexbox', testAllProps('flexBasis', '1px', !0)), + Modernizr.addTest( + 'flexboxlegacy', + testAllProps('boxDirection', 'reverse', !0) + ), + Modernizr.addTest('flexboxtweener', testAllProps('flexAlign', 'end', !0)), + Modernizr.addTest('flexwrap', testAllProps('flexWrap', 'wrap', !0)), + Modernizr.addAsyncTest(function () { + function A() { + function t() { + try { + var A = createElement('div'), + e = createElement('span'), + t = A.style, + r = 0, + n = 0, + o = !1, + i = document.body.firstElementChild || document.body.firstChild; + return ( + A.appendChild(e), + (e.innerHTML = + 'Bacon ipsum dolor sit amet jerky velit in culpa hamburger et. Laborum dolor proident, enim dolore duis commodo et strip steak. Salami anim et, veniam consectetur dolore qui tenderloin jowl velit sirloin. Et ad culpa, fatback cillum jowl ball tip ham hock nulla short ribs pariatur aute. Pig pancetta ham bresaola, ut boudin nostrud commodo flank esse cow tongue culpa. Pork belly bresaola enim pig, ea consectetur nisi. Fugiat officia turkey, ea cow jowl pariatur ullamco proident do laborum velit sausage. Magna biltong sint tri-tip commodo sed bacon, esse proident aliquip. Ullamco ham sint fugiat, velit in enim sed mollit nulla cow ut adipisicing nostrud consectetur. Proident dolore beef ribs, laborum nostrud meatball ea laboris rump cupidatat labore culpa. Shankle minim beef, velit sint cupidatat fugiat tenderloin pig et ball tip. Ut cow fatback salami, bacon ball tip et in shank strip steak bresaola. In ut pork belly sed mollit tri-tip magna culpa veniam, short ribs qui in andouille ham consequat. Dolore bacon t-bone, velit short ribs enim strip steak nulla. Voluptate labore ut, biltong swine irure jerky. Cupidatat excepteur aliquip salami dolore. Ball tip strip steak in pork dolor. Ad in esse biltong. Dolore tenderloin exercitation ad pork loin t-bone, dolore in chicken ball tip qui pig. Ut culpa tongue, sint ribeye dolore ex shank voluptate hamburger. Jowl et tempor, boudin pork chop labore ham hock drumstick consectetur tri-tip elit swine meatball chicken ground round. Proident shankle mollit dolore. Shoulder ut duis t-bone quis reprehenderit. Meatloaf dolore minim strip steak, laboris ea aute bacon beef ribs elit shank in veniam drumstick qui. Ex laboris meatball cow tongue pork belly. Ea ball tip reprehenderit pig, sed fatback boudin dolore flank aliquip laboris eu quis. Beef ribs duis beef, cow corned beef adipisicing commodo nisi deserunt exercitation. Cillum dolor t-bone spare ribs, ham hock est sirloin. Brisket irure meatloaf in, boudin pork belly sirloin ball tip. Sirloin sint irure nisi nostrud aliqua. Nostrud nulla aute, enim officia culpa ham hock. Aliqua reprehenderit dolore sunt nostrud sausage, ea boudin pork loin ut t-bone ham tempor. Tri-tip et pancetta drumstick laborum. Ham hock magna do nostrud in proident. Ex ground round fatback, venison non ribeye in.'), + document.body.insertBefore(A, i), + (t.cssText = + 'position:absolute;top:0;left:0;width:5em;text-align:justify;text-justification:newspaper;'), + (r = e.offsetHeight), + (n = e.offsetWidth), + (t.cssText = + 'position:absolute;top:0;left:0;width:5em;text-align:justify;text-justification:newspaper;' + + prefixes.join('hyphens:auto; ')), + (o = e.offsetHeight != r || e.offsetWidth != n), + document.body.removeChild(A), + A.removeChild(e), + o + ); + } catch (d) { + return !1; + } + } + function r(A, e) { + try { + var t = createElement('div'), + r = createElement('span'), + n = t.style, + o = 0, + i = !1, + d = !1, + a = !1, + s = document.body.firstElementChild || document.body.firstChild; + return ( + (n.cssText = + 'position:absolute;top:0;left:0;overflow:visible;width:1.25em;'), + t.appendChild(r), + document.body.insertBefore(t, s), + (r.innerHTML = 'mm'), + (o = r.offsetHeight), + (r.innerHTML = 'm' + A + 'm'), + (d = r.offsetHeight > o), + e + ? ((r.innerHTML = 'm
    m'), + (o = r.offsetWidth), + (r.innerHTML = 'm' + A + 'm'), + (a = r.offsetWidth > o)) + : (a = !0), + d === !0 && a === !0 && (i = !0), + document.body.removeChild(t), + t.removeChild(r), + i + ); + } catch (l) { + return !1; + } + } + function n(A) { + try { + var e, + t = createElement('input'), + r = createElement('div'), + n = 'lebowski', + o = !1, + i = document.body.firstElementChild || document.body.firstChild; + ((r.innerHTML = n + A + n), + document.body.insertBefore(r, i), + document.body.insertBefore(t, r), + t.setSelectionRange + ? (t.focus(), t.setSelectionRange(0, 0)) + : t.createTextRange && + ((e = t.createTextRange()), + e.collapse(!0), + e.moveEnd('character', 0), + e.moveStart('character', 0), + e.select())); + try { + window.find + ? (o = window.find(n + n)) + : ((e = window.self.document.body.createTextRange()), + (o = e.findText(n + n))); + } catch (d) { + o = !1; + } + return ( + document.body.removeChild(r), document.body.removeChild(t), o + ); + } catch (d) { + return !1; + } + } + return document.body || document.getElementsByTagName('body')[0] + ? (addTest('csshyphens', function () { + if (!testAllProps('hyphens', 'auto', !0)) return !1; + try { + return t(); + } catch (A) { + return !1; + } + }), + addTest('softhyphens', function () { + try { + return r('­', !0) && r('​', !1); + } catch (A) { + return !1; + } + }), + void addTest('softhyphensfind', function () { + try { + return n('­') && n('​'); + } catch (A) { + return !1; + } + })) + : void setTimeout(A, e); + } + var e = 300; + setTimeout(A, e); + }), + Modernizr.addTest('cssmask', testAllProps('maskRepeat', 'repeat-x', !0)), + Modernizr.addTest( + 'overflowscrolling', + testAllProps('overflowScrolling', 'touch', !0) + ), + Modernizr.addTest( + 'cssreflections', + testAllProps('boxReflect', 'above', !0) + ), + Modernizr.addTest('cssresize', testAllProps('resize', 'both', !0)), + Modernizr.addTest( + 'shapes', + testAllProps('shapeOutside', 'content-box', !0) + ), + Modernizr.addTest('textalignlast', testAllProps('textAlignLast')), + Modernizr.addTest('csstransforms', function () { + return ( + -1 === navigator.userAgent.indexOf('Android 2.') && + testAllProps('transform', 'scale(1)', !0) + ); + }), + Modernizr.addTest('csstransforms3d', function () { + var A = !!testAllProps('perspective', '1px', !0), + e = Modernizr._config.usePrefixes; + if (A && (!e || 'webkitPerspective' in docElement.style)) { + var t, + r = '#modernizr{width:0;height:0}'; + (Modernizr.supports + ? (t = '@supports (perspective: 1px)') + : ((t = '@media (transform-3d)'), + e && (t += ',(-webkit-transform-3d)')), + (t += + '{#modernizr{width:7px;height:18px;margin:0;padding:0;border:0}}'), + testStyles(r + t, function (e) { + A = 7 === e.offsetWidth && 18 === e.offsetHeight; + })); + } + return A; + }), + Modernizr.addTest('csstransitions', testAllProps('transition', 'all', !0)), + Modernizr.addTest('csspseudotransitions', function () { + var A = !1; + if (!Modernizr.csstransitions || !window.getComputedStyle) return A; + var e = + '#modernizr:before { content:" "; font-size:5px;' + + Modernizr._prefixes.join('transition:0s 100s;') + + '}#modernizr.trigger:before { font-size:10px; }'; + return ( + Modernizr.testStyles(e, function (e) { + (window.getComputedStyle(e, ':before').getPropertyValue('font-size'), + (e.className += 'trigger'), + (A = + '5px' === + window + .getComputedStyle(e, ':before') + .getPropertyValue('font-size'))); + }), + A + ); + }), + Modernizr.addTest('userselect', testAllProps('userSelect', 'none', !0)), + testRunner(), + setClasses(classes), + delete ModernizrProto.addTest, + delete ModernizrProto.addAsyncTest); + for (var i = 0; i < Modernizr._q.length; i++) Modernizr._q[i](); + window.Modernizr = Modernizr; +})(window, document); diff --git a/test/js/p5_helpers.js b/test/js/p5_helpers.js index 363c0d462e..27c08564dc 100644 --- a/test/js/p5_helpers.js +++ b/test/js/p5_helpers.js @@ -3,23 +3,25 @@ import p5 from '../../src/app.js'; export function promisedSketch(sketch_fn) { var myInstance; - var promise = new Promise(function(resolve, reject) { - myInstance = new p5(function(sketch) { + var promise = new Promise(function (resolve, reject) { + myInstance = new p5(function (sketch) { return sketch_fn(sketch, resolve, reject); }); }); - promise.catch(function() {}).then(function() { - myInstance.remove(); - }); + promise + .catch(function () {}) + .then(function () { + myInstance.remove(); + }); return promise; } export function testSketchWithPromise(name, sketch_fn) { - var test_fn = function() { + var test_fn = function () { return promisedSketch(sketch_fn); }; - test_fn.toString = function() { + test_fn.toString = function () { return sketch_fn.toString(); }; return test(name, test_fn); @@ -35,11 +37,11 @@ export function parallelSketches(sketch_fns) { var resultPromises = []; var endCallbacks = []; for (let i = 0; i < sketch_fns.length; i++) { - setupPromises[i] = new Promise(function(resolve) { - resultPromises[i] = promisedSketch(function(sketch, _resolve, _reject) { + setupPromises[i] = new Promise(function (resolve) { + resultPromises[i] = promisedSketch(function (sketch, _resolve, _reject) { sketch_fns[i](sketch, _resolve, _reject); var old_setup = sketch.setup; - sketch.setup = function() { + sketch.setup = function () { if (old_setup) { old_setup(); } @@ -82,7 +84,7 @@ export function createP5Iframe(html) { return { elt: elt, - teardown: function() { + teardown: function () { elt.parentNode.removeChild(elt); } }; diff --git a/test/manual-test-examples/type/vite.config.mjs b/test/manual-test-examples/type/vite.config.mjs index 301192ed7f..c3ae66701e 100644 --- a/test/manual-test-examples/type/vite.config.mjs +++ b/test/manual-test-examples/type/vite.config.mjs @@ -1,10 +1,10 @@ import { defineConfig } from 'vitest/config'; -import vitePluginString from 'vite-plugin-string'; +import { string } from 'rollup-plugin-string'; export default defineConfig({ root: './', plugins: [ - vitePluginString({ + string({ include: [ 'src/webgl/shaders/**/*' ] diff --git a/test/mockServiceWorker.js b/test/mockServiceWorker.js index ec47a9a50a..0c970efc96 100644 --- a/test/mockServiceWorker.js +++ b/test/mockServiceWorker.js @@ -5,24 +5,23 @@ * Mock Service Worker. * @see https://github.com/mswjs/msw * - Please do NOT modify this file. - * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.7.0' -const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' +const PACKAGE_VERSION = '2.15.0' +const INTEGRITY_CHECKSUM = '03cb67ac84128e63d7cd722a6e5b7f1e' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() -self.addEventListener('install', function () { +addEventListener('install', function () { self.skipWaiting() }) -self.addEventListener('activate', function (event) { +addEventListener('activate', function (event) { event.waitUntil(self.clients.claim()) }) -self.addEventListener('message', async function (event) { - const clientId = event.source.id +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') if (!clientId || !self.clients) { return @@ -72,11 +71,6 @@ self.addEventListener('message', async function (event) { break } - case 'MOCK_DEACTIVATE': { - activeClientIds.delete(clientId) - break - } - case 'CLIENT_CLOSED': { activeClientIds.delete(clientId) @@ -94,69 +88,104 @@ self.addEventListener('message', async function (event) { } }) -self.addEventListener('fetch', function (event) { - const { request } = event +addEventListener('fetch', function (event) { + const requestInterceptedAt = Date.now() // Bypass navigation requests. - if (request.mode === 'navigate') { + if (event.request.mode === 'navigate') { return } // Opening the DevTools triggers the "only-if-cached" request // that cannot be handled by the worker. Bypass such requests. - if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + if ( + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' + ) { return } // Bypass all requests when there are no active clients. // Prevents the self-unregistered worked from handling requests - // after it's been deleted (still remains active until the next reload). + // after it's been terminated (still remains active until the next reload). if (activeClientIds.size === 0) { return } - // Generate unique request ID. const requestId = crypto.randomUUID() - event.respondWith(handleRequest(event, requestId)) + event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) }) -async function handleRequest(event, requestId) { +/** + * @param {FetchEvent} event + * @param {string} requestId + * @param {number} requestInterceptedAt + */ +async function handleRequest(event, requestId, requestInterceptedAt) { const client = await resolveMainClient(event) - const response = await getResponse(event, client, requestId) + const requestCloneForEvents = event.request.clone() + const response = await getResponse( + event, + client, + requestId, + requestInterceptedAt, + ) // Send back the response clone for the "response:*" life-cycle events. // Ensure MSW is active and ready to handle the message, otherwise // this message will pend indefinitely. if (client && activeClientIds.has(client.id)) { - ;(async function () { - const responseClone = response.clone() - - sendToClient( - client, - { - type: 'RESPONSE', - payload: { - requestId, - isMockedResponse: IS_MOCKED_RESPONSE in response, - type: responseClone.type, - status: responseClone.status, - statusText: responseClone.statusText, - body: responseClone.body, - headers: Object.fromEntries(responseClone.headers.entries()), + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Omit the body of server-sent event stream responses. + // Cloning such responses would prevent client-side stream cancelations + // from reaching the original stream (a teed stream only cancels its + // source once both of its branches cancel) and would buffer the + // entire stream into the unconsumed clone indefinitely. + const isEventStreamResponse = response.headers + .get('content-type') + ?.toLowerCase() + .startsWith('text/event-stream') + + // Clone the response so both the client and the library could consume it. + const responseClone = isEventStreamResponse ? null : response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: response.type, + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()), + body: responseClone ? responseClone.body : null, }, }, - [responseClone.body], - ) - })() + }, + responseClone && responseClone.body + ? [serializedRequest.body, responseClone.body] + : [], + ) } return response } -// Resolve the main client for the given event. -// Client that issues a request doesn't necessarily equal the client -// that registered the worker. It's with the latter the worker should -// communicate with during the response resolving phase. +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ async function resolveMainClient(event) { const client = await self.clients.get(event.clientId) @@ -184,12 +213,17 @@ async function resolveMainClient(event) { }) } -async function getResponse(event, client, requestId) { - const { request } = event - +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @param {number} requestInterceptedAt + * @returns {Promise} + */ +async function getResponse(event, client, requestId, requestInterceptedAt) { // Clone the request because it might've been already used // (i.e. its body has been read and sent to the client). - const requestClone = request.clone() + const requestClone = event.request.clone() function passthrough() { // Cast the request headers to a new Headers instance @@ -230,29 +264,18 @@ async function getResponse(event, client, requestId) { } // Notify the client that a request has been intercepted. - const requestBuffer = await request.arrayBuffer() + const serializedRequest = await serializeRequest(event.request) const clientMessage = await sendToClient( client, { type: 'REQUEST', payload: { id: requestId, - url: request.url, - mode: request.mode, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: requestBuffer, - keepalive: request.keepalive, + interceptedAt: requestInterceptedAt, + ...serializedRequest, }, }, - [requestBuffer], + [serializedRequest.body], ) switch (clientMessage.type) { @@ -268,6 +291,12 @@ async function getResponse(event, client, requestId) { return passthrough() } +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ function sendToClient(client, message, transferrables = []) { return new Promise((resolve, reject) => { const channel = new MessageChannel() @@ -280,14 +309,18 @@ function sendToClient(client, message, transferrables = []) { resolve(event.data) } - client.postMessage( - message, - [channel.port2].concat(transferrables.filter(Boolean)), - ) + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) }) } -async function respondWithMock(response) { +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { // Setting response status code to 0 is a no-op. // However, when responding with a "Response.error()", the produced Response // instance will have status code set to 0. Since it's not possible to create @@ -305,3 +338,24 @@ async function respondWithMock(response) { return mockedResponse } + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/test/node/helpers.js b/test/node/helpers.js index c2d4814378..d185d53e00 100644 --- a/test/node/helpers.js +++ b/test/node/helpers.js @@ -8,20 +8,20 @@ const c = 50; const d = 150; let result; -suite('helpers/modeAdjust', function() { - test('should set mode to corner', function() { +suite('helpers/modeAdjust', function () { + test('should set mode to corner', function () { result = helpers.modeAdjust(a, b, c, d, constants.CORNER); expect(result).to.eql({ x: 100, y: 200, w: 50, h: 150 }); }); - test('should set mode to corners', function() { + test('should set mode to corners', function () { result = helpers.modeAdjust(a, b, c, d, constants.CORNERS); expect(result).to.eql({ x: 50, y: 150, w: 50, h: 50 }); }); - test('should set mode to radius', function() { + test('should set mode to radius', function () { result = helpers.modeAdjust(a, b, c, d, constants.RADIUS); expect(result).to.eql({ x: 50, y: 50, w: 100, h: 300 }); }); - test('should set mode to center', function() { + test('should set mode to center', function () { result = helpers.modeAdjust(a, b, c, d, constants.CENTER); expect(result).to.eql({ x: 75, y: 125, w: 50, h: 150 }); }); diff --git a/test/node/test-docs-preprocessor.js b/test/node/test-docs-preprocessor.js index ba5c854ece..56511748e9 100644 --- a/test/node/test-docs-preprocessor.js +++ b/test/node/test-docs-preprocessor.js @@ -1,17 +1,17 @@ import { expect } from 'chai'; import preprocessor from '../../docs/preprocessor'; -suite('docs preprocessor', function() { - suite('mergeOverloadedMethods()', function() { +suite('docs preprocessor', function () { + suite('mergeOverloadedMethods()', function () { const merge = preprocessor.mergeOverloadedMethods; - const ensureMergeDoesNothing = function(data) { + const ensureMergeDoesNothing = function (data) { const dataCopy = JSON.parse(JSON.stringify(data)); merge(dataCopy); expect(dataCopy).to.eql(data); }; - test('should merge methods with the same name', function() { + test('should merge methods with the same name', function () { const data = { classes: { Bar: {}, @@ -70,7 +70,7 @@ suite('docs preprocessor', function() { }); }); - test('should not merge methods from different classes', function() { + test('should not merge methods from different classes', function () { ensureMergeDoesNothing({ classes: { Bar: {}, @@ -84,7 +84,7 @@ suite('docs preprocessor', function() { }); }); - test('should not merge properties', function() { + test('should not merge properties', function () { ensureMergeDoesNothing({ classes: { Bar: {}, @@ -99,10 +99,10 @@ suite('docs preprocessor', function() { }); }); - suite('renderDescriptionsAsMarkdown', function() { + suite('renderDescriptionsAsMarkdown', function () { const render = preprocessor.renderDescriptionsAsMarkdown; - test('should work', function() { + test('should work', function () { const data = { modules: {}, classes: { diff --git a/test/types/basic.ts b/test/types/basic.ts index 7398596f74..0b662297a8 100644 --- a/test/types/basic.ts +++ b/test/types/basic.ts @@ -1,77 +1,73 @@ // Modified from https://openprocessing.org/sketch/2500100 -import p5 from '../../types/global' +import p5 from '../../types/global'; -p5.disableFriendlyErrors = true +p5.disableFriendlyErrors = true; -let geom: p5.Geometry -let geom2: p5.Geometry +let geom: p5.Geometry; +let geom2: p5.Geometry; function setup() { - createCanvas(windowWidth, windowHeight, WEBGL) - geom2 = new p5.Geometry(1, 1, function() { - this.vertices.push(createVector(0, 0, 0)) - this.vertices.push(createVector(1, 0, 0)) - this.vertices.push(createVector(1, 1, 0)) - this.faces.push([0, 1, 2]) - this.computeNormals() - }) + createCanvas(windowWidth, windowHeight, WEBGL); + geom2 = new p5.Geometry(1, 1, function () { + this.vertices.push(createVector(0, 0, 0)); + this.vertices.push(createVector(1, 0, 0)); + this.vertices.push(createVector(1, 1, 0)); + this.faces.push([0, 1, 2]); + this.computeNormals(); + }); } function regenerate() { - if (geom) { - freeGeometry(geom) - } - geom = buildGeometry(() => { - let n = round(random(5, 20)) - for (let i = 0; i <= n; i++) { - push() - translate( - random(-1, 1)*width*0.05, - map(i, 0, n, height*0.4, -height*0.4) + random(-1,1)*height*0.05 - ) - rotateX(PI/2 + random(-1, 1) * PI * 0.15) - rotateZ(random(-1, 1) * PI * 0.15) - torus( - random(0.1, 0.3) * width, - random(0.01, 0.05) * width, - 50, - 30 - ) - pop() - } - }) - geom.clearColors() + if (geom) { + freeGeometry(geom); + } + geom = buildGeometry(() => { + let n = round(random(5, 20)); + for (let i = 0; i <= n; i++) { + push(); + translate( + random(-1, 1) * width * 0.05, + map(i, 0, n, height * 0.4, -height * 0.4) + + random(-1, 1) * height * 0.05 + ); + rotateX(PI / 2 + random(-1, 1) * PI * 0.15); + rotateZ(random(-1, 1) * PI * 0.15); + torus(random(0.1, 0.3) * width, random(0.01, 0.05) * width, 50, 30); + pop(); + } + }); + geom.clearColors(); } -let lastScene = -1 +let lastScene = -1; function draw() { - const period = 8000 - - const ms = millis() - const scene = floor(ms / period) - if (scene !== lastScene) { - regenerate() - lastScene = scene - } - - const t = (ms % period)/period - background(0) - orbitControl() - const s = map(t, 0, 0.2, 0, 1, true) * map(t, 0.8, 1, 1, 0, true) - directionalLight(s*255, s*255, s*255, -0.4, 0, 1) - directionalLight(s*255, s*255, s*255, 0.4, 0, 1) - directionalLight(s*255, s*255, s*255, 0, -0.4, 1) - directionalLight(s*255, s*255, s*255, 0, 0.4, 1) - noStroke() - fill(100) - specularMaterial(255) - shininess(400) - scale(0.8) - rotateY(millis() * 0.0001) - model(geom) + const period = 8000; + + const ms = millis(); + const scene = floor(ms / period); + if (scene !== lastScene) { + regenerate(); + lastScene = scene; + } + + const t = (ms % period) / period; + background(0); + orbitControl(); + const s = map(t, 0, 0.2, 0, 1, true) * map(t, 0.8, 1, 1, 0, true); + directionalLight(s * 255, s * 255, s * 255, -0.4, 0, 1); + directionalLight(s * 255, s * 255, s * 255, 0.4, 0, 1); + directionalLight(s * 255, s * 255, s * 255, 0, -0.4, 1); + directionalLight(s * 255, s * 255, s * 255, 0, 0.4, 1); + noStroke(); + fill(100); + specularMaterial(255); + shininess(400); + scale(0.8); + rotateY(millis() * 0.0001); + model(geom); } -function mousePressed(){ - //only type-checking the relevant properties exist on the mouseButton object type. - const whatever = mouseButton.left || mouseButton.center || mouseButton.right; -} \ No newline at end of file +function mousePressed() { + //only type-checking the relevant properties exist on the mouseButton object type. + const whatever = mouseButton.left || mouseButton.center || mouseButton.right; +} diff --git a/test/types/generics.ts b/test/types/generics.ts index f59f575863..0540179d50 100644 --- a/test/types/generics.ts +++ b/test/types/generics.ts @@ -1,53 +1,56 @@ -import '../../types/global' +import '../../types/global'; function setup() { - noCanvas() + noCanvas(); const messages = [ { content: 'Hello, world!' }, - { content: "How's it going?" }, - ] - const message = random(messages) + { content: "How's it going?" } + ]; + const message = random(messages); // The types should fail if the result of random() is any logMessage(message); - + testShuffleMaintainsType(); } // From: https://stackoverflow.com/a/50375286/62076 -type UnionToIntersection = - (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I +) => void + ? I + : never; // If T is `any` a union of both side of the condition is returned. -type UnionForAny = T extends never ? 'A' : 'B' +type UnionForAny = T extends never ? 'A' : 'B'; // Returns true if type is any, or false for any other type. type IsStrictlyAny = - UnionToIntersection> extends never ? true : false + UnionToIntersection> extends never ? true : false; -type NotAny = IsStrictlyAny extends true ? never : T +type NotAny = IsStrictlyAny extends true ? never : T; function logMessage(message: NotAny<{ content: string }>) { - console.log(message) + console.log(message); } - - /** test that shuffle(arr) preserves arr type in return, not just any[] */ -function testShuffleMaintainsType(){ - - type Expect = T; - type Equal = - (() => T extends X ? 1 : 2) extends - (() => T extends Y ? 1 : 2) ? true : false; - - const shuffleResult1 = shuffle(["a", "b", "c"]); - const shuffleResult2 = shuffle(["a", 10, null]); +function testShuffleMaintainsType() { + type Expect = T; + type Equal = + (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 + ? true + : false; + + const shuffleResult1 = shuffle(['a', 'b', 'c']); + const shuffleResult2 = shuffle(['a', 10, null]); //check the signature with the optional boolean type-checks, too - const shuffleResult3 = shuffle([10, 20, 30], true); + const shuffleResult3 = shuffle([10, 20, 30], true); - type ShuffleTest1 = Expect>; - type ShuffleTest2 = Expect>; + type ShuffleTest1 = Expect>; + type ShuffleTest2 = Expect< + Equal + >; type ShuffleTest3 = Expect>; } diff --git a/test/types/instance.ts b/test/types/instance.ts index cd643d7ef9..5a00b3e11b 100644 --- a/test/types/instance.ts +++ b/test/types/instance.ts @@ -1,20 +1,20 @@ -import P5 from '../../types/p5' +import P5 from '../../types/p5'; -P5.disableFriendlyErrors = true +P5.disableFriendlyErrors = true; -const sketch = new P5((p) => { - let g: P5.Graphics - p.setup = function() { - p.createCanvas(200, 200) - g = p.createGraphics(200, 200) - } +const sketch = new P5(p => { + let g: P5.Graphics; + p.setup = function () { + p.createCanvas(200, 200); + g = p.createGraphics(200, 200); + }; - p.mouseMoved = function() { + p.mouseMoved = function () { g.circle(p.mouseX, p.mouseY, 20); - } + }; - p.draw = function() { - p.clear() - p.image(g, 0, 0) - } -}) + p.draw = function () { + p.clear(); + p.image(g, 0, 0); + }; +}); diff --git a/test/types/strands.ts b/test/types/strands.ts index 36397cbd58..ce3cfd1ffd 100644 --- a/test/types/strands.ts +++ b/test/types/strands.ts @@ -1,7 +1,7 @@ // Based on p5.strands: Introduction to Shaders By Luke Plowden // https://beta.p5js.org/tutorials/intro-to-p5-strands/ -import '../../types/global' +import '../../types/global'; let starShader: p5.Shader; let starStrokeShader: p5.Shader; @@ -16,12 +16,12 @@ function fresnelShaderCallback() { const fresnelBias = uniformFloat(-0.1); const fresnelScale = uniformFloat(2); - getCameraInputs((inputs) => { + getCameraInputs(inputs => { let n = normalize(inputs.normal); let v = normalize(-inputs.position); let base = 1.0 - dot(n, v); let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias; - let col = mix([0, 0, 0], [1, .5, .7], fresnel); + let col = mix([0, 0, 0], [1, 0.5, 0.7], fresnel); inputs.color = [col, 1]; return inputs; }); @@ -35,7 +35,7 @@ function starShaderCallback() { const testSharedVector = sharedVector2(); const testVaryingVec = varyingVec2(); const testVaryingVector = varyingVector2(); - + function rand2(st) { return fract(sin(dot(st, [12.9898, 78.233])) * 43758.5453123); } @@ -43,9 +43,9 @@ function starShaderCallback() { function semiSphere() { let id = instanceID(); let idx = instanceIndex; - let theta = rand2([id, 0.1234]) * TWO_PI + time / 100000; + let theta = rand2([id, 0.1234]) * TWO_PI + time / 100000; let phi = rand2([id, 3.321]) * PI + time / 50000; - + let r = skyRadius; r *= sin(phi); let x = r * sin(phi) * cos(theta); @@ -54,12 +54,12 @@ function starShaderCallback() { return [x, y, z]; } - getWorldInputs((inputs) => { + getWorldInputs(inputs => { inputs.position += semiSphere(); return inputs; }); - getObjectInputs((inputs) => { + getObjectInputs(inputs => { let size = 1 + 0.5 * sin(time * 0.002 + instanceID()); inputs.position *= size; return inputs; @@ -67,7 +67,7 @@ function starShaderCallback() { } function pixelateShaderCallback() { - const pixelCountX = uniformFloat(()=> 280); + const pixelCountX = uniformFloat(() => 280); getColor((inputs, canvasContent) => { const aspectRatio = inputs.canvasSize.x / inputs.canvasSize.y; @@ -78,7 +78,7 @@ function pixelateShaderCallback() { coord = floor(coord * pixelSize) / pixelSize; let col = getTexture(canvasContent, coord); - return col//[coord, 0, 1]; + return col; //[coord, 0, 1]; }); } @@ -90,52 +90,52 @@ function bloomShaderCallback() { const originalCol = getTexture(preBlur, input.texCoord); const intensity = max(originalCol, 0.1) * 12.2; - + const bloom = originalCol + blurredCol * intensity; return [bloom.rgb, 1]; }); } -async function setup(){ +async function setup() { createCanvas(800, 600, WEBGL); pixelDensity(1); - stars = buildGeometry(() => sphere(8, 4, 2)) + stars = buildGeometry(() => sphere(8, 4, 2)); originalImage = createFramebuffer(); - starShader = baseMaterialShader().modify(starShaderCallback); - starStrokeShader = baseStrokeShader().modify(starShaderCallback) + starShader = baseMaterialShader().modify(starShaderCallback); + starStrokeShader = baseStrokeShader().modify(starShaderCallback); fresnelShader = baseColorShader().modify(fresnelShaderCallback); bloomShader = baseFilterShader().modify(bloomShaderCallback); pixelateShader = baseFilterShader().modify(pixelateShaderCallback); } -function draw(){ +function draw() { originalImage.begin(); background(0); orbitControl(); - push() - strokeWeight(2) - stroke(255,0,0) - rotateX(PI/2 + millis() * 0.0005); - fill(255,100, 150) - strokeShader(starStrokeShader) + push(); + strokeWeight(2); + stroke(255, 0, 0); + rotateX(PI / 2 + millis() * 0.0005); + fill(255, 100, 150); + strokeShader(starStrokeShader); shader(starShader); model(stars, 1000); - pop() + pop(); - push() - shader(fresnelShader) - noStroke() + push(); + shader(fresnelShader); + noStroke(); sphere(90); filter(pixelateShader); - pop() + pop(); originalImage.end(); - - imageMode(CENTER) - image(originalImage, 0, 0) - - filter(BLUR, 15) + + imageMode(CENTER); + image(originalImage, 0, 0); + + filter(BLUR, 15); filter(bloomShader); } diff --git a/test/types/typography.ts b/test/types/typography.ts index 972e43d0df..b150198ca5 100644 --- a/test/types/typography.ts +++ b/test/types/typography.ts @@ -1,73 +1,87 @@ // From https://openprocessing.org/sketch/2523015 -import '../../types/global' +import '../../types/global'; -let font: p5.Font -const txt = 'p5.js' +let font: p5.Font; +const txt = 'p5.js'; async function setup() { - createCanvas(windowWidth, windowHeight); - font = await loadFont('https://fonts.gstatic.com/s/sniglet/v17/cIf4MaFLtkE3UjaJ_ImHRGEsnIJkWL4.ttf') + createCanvas(windowWidth, windowHeight); + font = await loadFont( + 'https://fonts.gstatic.com/s/sniglet/v17/cIf4MaFLtkE3UjaJ_ImHRGEsnIJkWL4.ttf' + ); } -let prevTxt = '' -let prevTxtTime = 0 +let prevTxt = ''; +let prevTxtTime = 0; function draw() { - if (txt !== prevTxt) { - prevTxt = txt - prevTxtTime = millis() - } - const progress = pow(map(millis(), prevTxtTime, prevTxtTime + 2000, 0, 1, true), 0.5) - - const contours = font.textToContours(txt, 0, 0, { sampleFactor: 1 }) - - background(0) - textAlign(CENTER, CENTER) - textSize(120) - textFont(font) - translate(width/2, height/2) - scale(min(width, height)/300) - const w = max(10, fontWidth(txt)) - scale((width * 0.2) / w) - - beginShape() - for (const contour of contours) { - beginContour() - for (const pt of contour) { - vertex(pt.x, pt.y) - } - endContour(CLOSE) - } - endShape() - - push() - strokeWeight(0.5) - stroke('rgb(255,127,228)') - noFill() + if (txt !== prevTxt) { + prevTxt = txt; + prevTxtTime = millis(); + } + const progress = pow( + map(millis(), prevTxtTime, prevTxtTime + 2000, 0, 1, true), + 0.5 + ); - beginShape(LINES) - for (const contour of contours) { - const pts = contour.map((v) => createVector(v.x, v.y)) - if (pts[0].dist(pts.at(-1)) === 0) pts.pop() - const dists = pts.map((pt, i) => max(1e-6, pt.dist(pts[(i+1)%pts.length]))) - - let tangents = pts.map((v, i) => pts[(i+1)%pts.length].copy().sub(v).div(dists[i])) - for (let it = 0; it < 2; it++) { - tangents = tangents.map( - (tangent, i) => - tangent.copy() - .add(tangents[(i-1+pts.length)%pts.length]) - .add(tangents[(i+1)%pts.length]) - .mult(1/3) - ) - } - - const ks = tangents.map((t, i) => tangents[(i+1)%pts.length].copy().sub(t)) - - pts.forEach((pt, i) => { - vertex(pt.x, pt.y) - vertex(pt.x + ks[i].x * -120 * progress, pt.y + ks[i].y * -120 * progress) - }) - } - endShape() - pop() + const contours = font.textToContours(txt, 0, 0, { sampleFactor: 1 }); + + background(0); + textAlign(CENTER, CENTER); + textSize(120); + textFont(font); + translate(width / 2, height / 2); + scale(min(width, height) / 300); + const w = max(10, fontWidth(txt)); + scale((width * 0.2) / w); + + beginShape(); + for (const contour of contours) { + beginContour(); + for (const pt of contour) { + vertex(pt.x, pt.y); + } + endContour(CLOSE); + } + endShape(); + + push(); + strokeWeight(0.5); + stroke('rgb(255,127,228)'); + noFill(); + + beginShape(LINES); + for (const contour of contours) { + const pts = contour.map(v => createVector(v.x, v.y)); + if (pts[0].dist(pts.at(-1)) === 0) pts.pop(); + const dists = pts.map((pt, i) => + max(1e-6, pt.dist(pts[(i + 1) % pts.length])) + ); + + let tangents = pts.map((v, i) => + pts[(i + 1) % pts.length].copy().sub(v).div(dists[i]) + ); + for (let it = 0; it < 2; it++) { + tangents = tangents.map((tangent, i) => + tangent + .copy() + .add(tangents[(i - 1 + pts.length) % pts.length]) + .add(tangents[(i + 1) % pts.length]) + .mult(1 / 3) + ); + } + + const ks = tangents.map((t, i) => + tangents[(i + 1) % pts.length].copy().sub(t) + ); + + pts.forEach((pt, i) => { + vertex(pt.x, pt.y); + vertex( + pt.x + ks[i].x * -120 * progress, + pt.y + ks[i].y * -120 * progress + ); + }); + } + endShape(); + pop(); } diff --git a/test/types/webgl-pixels.ts b/test/types/webgl-pixels.ts index 8c60186372..4b535604d7 100644 --- a/test/types/webgl-pixels.ts +++ b/test/types/webgl-pixels.ts @@ -1,211 +1,244 @@ // From https://openprocessing.org/sketch/2308573 -import '../../types/global' +import '../../types/global'; // Fake matter.js import -declare const Matter: any +declare const Matter: any; -let engine: any -let blobs: Blob[] = [] -let metaballShader: p5.Shader -let spheremap: p5.Image -let renderer: p5.RendererGL +let engine: any; +let blobs: Blob[] = []; +let metaballShader: p5.Shader; +let spheremap: p5.Image; +let renderer: p5.RendererGL; async function setup() { - spheremap = await loadImage('https://deckard.openprocessing.org/user67809/visual2181338/h987a85d77bacbc3b232fb87ce6fe440a/dusseldorf_bridge.jpg') - renderer = createCanvas(600, 600, WEBGL) - metaballShader = createShader(vert, frag) - setupScene() - blobs.push(new Blob(random(-1,1)*100, 50, 100, '#f3e17e')) - blobs.push(new Blob(random(-1,1)*100, -150, 100, '#dd483c')) - blobs.push(new Blob(random(-1,1)*100, -350, 50, '#4b8a5f')) - blobs.push(new Blob(random(-1,1)*100, -550, 50, '#0d150b')) + spheremap = await loadImage( + 'https://deckard.openprocessing.org/user67809/visual2181338/h987a85d77bacbc3b232fb87ce6fe440a/dusseldorf_bridge.jpg' + ); + renderer = createCanvas(600, 600, WEBGL); + metaballShader = createShader(vert, frag); + setupScene(); + blobs.push(new Blob(random(-1, 1) * 100, 50, 100, '#f3e17e')); + blobs.push(new Blob(random(-1, 1) * 100, -150, 100, '#dd483c')); + blobs.push(new Blob(random(-1, 1) * 100, -350, 50, '#4b8a5f')); + blobs.push(new Blob(random(-1, 1) * 100, -550, 50, '#0d150b')); } function setupScene() { - engine = Matter.Engine.create() - - const ground = Matter.Bodies.rectangle(0, height / 2 + 30, width, 60, { - isStatic: true, - }) - const wallLeft = Matter.Bodies.rectangle(-width/2 - 30, 0, 60, 3 * height, { - isStatic: true, - }) - const wallRight = Matter.Bodies.rectangle(width/2 + 30, 0, 60, 3 * height, { - isStatic: true, - }) - Matter.World.add(engine.world, [ground, wallLeft, wallRight]) + engine = Matter.Engine.create(); + + const ground = Matter.Bodies.rectangle(0, height / 2 + 30, width, 60, { + isStatic: true + }); + const wallLeft = Matter.Bodies.rectangle(-width / 2 - 30, 0, 60, 3 * height, { + isStatic: true + }); + const wallRight = Matter.Bodies.rectangle(width / 2 + 30, 0, 60, 3 * height, { + isStatic: true + }); + Matter.World.add(engine.world, [ground, wallLeft, wallRight]); } function draw() { - background('#faf8e2') - // translate(width/2, height/2) - - for (const blob of blobs) { - blob.update() - } - Matter.Engine.update(engine, 1000 / 60) - - for (const blob of blobs) { - blob.drawBlob() - } + background('#faf8e2'); + // translate(width/2, height/2) + + for (const blob of blobs) { + blob.update(); + } + Matter.Engine.update(engine, 1000 / 60); + + for (const blob of blobs) { + blob.drawBlob(); + } } -const BLOB_NODE_SIZE = 20 -const BLOB_NODE_R = 15 -const BLOB_NODE_AREA = Math.PI * BLOB_NODE_SIZE * BLOB_NODE_SIZE +const BLOB_NODE_SIZE = 20; +const BLOB_NODE_R = 15; +const BLOB_NODE_AREA = Math.PI * BLOB_NODE_SIZE * BLOB_NODE_SIZE; class Blob { - c: p5.Color - nodes: any[] - springs: any[] - tex: p5.Image - - constructor(x, y, r, c) { - this.nodes = [] - this.springs = [] - this.c = color(c) - this.tex = createImage(20, 20) - this.tex.loadPixels() - for (let i = 0; i < this.tex.pixels.length; i++) { - this.tex.pixels[i] = 255 - } + c: p5.Color; + nodes: any[]; + springs: any[]; + tex: p5.Image; + + constructor(x, y, r, c) { + this.nodes = []; + this.springs = []; + this.c = color(c); + this.tex = createImage(20, 20); + this.tex.loadPixels(); + for (let i = 0; i < this.tex.pixels.length; i++) { + this.tex.pixels[i] = 255; + } // @ts-ignore - renderer.getTexture(this.tex).setInterpolation(NEAREST, NEAREST) - - const a = PI * r * r - const numBlobs = ceil(a / BLOB_NODE_AREA) - - while (this.nodes.length < numBlobs) { - const rx = random(-r, r) - const ry = random(-r, r) - if (Math.hypot(rx, ry) > r) continue - - const vert = Matter.Bodies.circle(x + rx, y + ry, BLOB_NODE_R, { inertia: Infinity, friction: 0.015 }) - this.nodes.push(vert) - } - - Matter.World.add(engine.world, this.nodes) - } - - bin(x: number, y: number) { - return [round(x/80), round(y/80)] - } - - nodeBin(node) { - return this.bin(node.position.x, node.position.y) - } - - adjacentBins(node) { - const [x, y] = this.nodeBin(node) - const bins: [number, number][] = [] - for (const dx of [-1, 0, 1]) { - for (const dy of [-1, 0, 1]) { - bins.push([x + dx, y + dy]) - } - } - return bins - } - - binKey(bin) { - return bin.join(',') - } - - binnedNodes() { - const bins = {} - for (const node of this.nodes) { - const binKey = this.binKey(this.nodeBin(node)) - if (!bins[binKey]) { - bins[binKey] = [] - } - bins[binKey].push(node) - } - return bins - } - - update() { - Matter.World.remove(engine.world, this.springs) - this.springs = [] - const bins = this.binnedNodes() - for (const node of this.nodes) { - const binsToCheck = this.adjacentBins(node) - for (const bin of binsToCheck) { - const key = this.binKey(bin) - if (!bins[key]) continue - for (const other of bins[key]) { - if (other === node) continue - this.springs.push(Matter.Constraint.create({ - bodyA: node, - pointA: { x: 0, y: 0 }, - bodyB: other, - pointB: { x: 0, y: 0 }, - stiffness: map( - Math.hypot(node.position.x - other.position.x, node.position.y - other.position.y), - 0, 12*BLOB_NODE_SIZE, - 0.02, 0.03, - true - ), - damping: 0.001, - // length: 0, - length: max( - 2 * BLOB_NODE_SIZE, - Math.hypot(node.position.x - other.position.x, node.position.y - other.position.y) * 0.975 - ), - })) - } - } - } - Matter.World.add(engine.world, this.springs) - } - - drawBlob() { - const minX = Math.min(...this.nodes.map((n) => n.position.x)) - 4 * BLOB_NODE_SIZE - const maxX = Math.max(...this.nodes.map((n) => n.position.x)) + 4 * BLOB_NODE_SIZE - const minY = Math.min(...this.nodes.map((n) => n.position.y)) - 4 * BLOB_NODE_SIZE - const maxY = Math.max(...this.nodes.map((n) => n.position.y)) + 4 * BLOB_NODE_SIZE - const x = (maxX + minX)/2 - const y = (maxY + minY)/2 - const w = maxX - minX - const h = maxY - minY - - this.nodes.forEach((node, i) => { - this.tex.pixels[i * 4 + 0] = map(node.position.x, minX, maxX, 0, 255, true) - this.tex.pixels[i * 4 + 1] = map(node.position.y, minY, maxY, 0, 255, true) - }) - this.tex.updatePixels() - - push() - translate(x, y) - noStroke() - shader(metaballShader) - metaballShader.setUniform('bbox', [minX, minY, maxX, maxY]) - metaballShader.setUniform('k', BLOB_NODE_SIZE * 3) - metaballShader.setUniform('numNodes', this.nodes.length) - metaballShader.setUniform('data', this.tex) - metaballShader.setUniform('r', BLOB_NODE_R) + renderer.getTexture(this.tex).setInterpolation(NEAREST, NEAREST); + + const a = PI * r * r; + const numBlobs = ceil(a / BLOB_NODE_AREA); + + while (this.nodes.length < numBlobs) { + const rx = random(-r, r); + const ry = random(-r, r); + if (Math.hypot(rx, ry) > r) continue; + + const vert = Matter.Bodies.circle(x + rx, y + ry, BLOB_NODE_R, { + inertia: Infinity, + friction: 0.015 + }); + this.nodes.push(vert); + } + + Matter.World.add(engine.world, this.nodes); + } + + bin(x: number, y: number) { + return [round(x / 80), round(y / 80)]; + } + + nodeBin(node) { + return this.bin(node.position.x, node.position.y); + } + + adjacentBins(node) { + const [x, y] = this.nodeBin(node); + const bins: [number, number][] = []; + for (const dx of [-1, 0, 1]) { + for (const dy of [-1, 0, 1]) { + bins.push([x + dx, y + dy]); + } + } + return bins; + } + + binKey(bin) { + return bin.join(','); + } + + binnedNodes() { + const bins = {}; + for (const node of this.nodes) { + const binKey = this.binKey(this.nodeBin(node)); + if (!bins[binKey]) { + bins[binKey] = []; + } + bins[binKey].push(node); + } + return bins; + } + + update() { + Matter.World.remove(engine.world, this.springs); + this.springs = []; + const bins = this.binnedNodes(); + for (const node of this.nodes) { + const binsToCheck = this.adjacentBins(node); + for (const bin of binsToCheck) { + const key = this.binKey(bin); + if (!bins[key]) continue; + for (const other of bins[key]) { + if (other === node) continue; + this.springs.push( + Matter.Constraint.create({ + bodyA: node, + pointA: { x: 0, y: 0 }, + bodyB: other, + pointB: { x: 0, y: 0 }, + stiffness: map( + Math.hypot( + node.position.x - other.position.x, + node.position.y - other.position.y + ), + 0, + 12 * BLOB_NODE_SIZE, + 0.02, + 0.03, + true + ), + damping: 0.001, + // length: 0, + length: max( + 2 * BLOB_NODE_SIZE, + Math.hypot( + node.position.x - other.position.x, + node.position.y - other.position.y + ) * 0.975 + ) + }) + ); + } + } + } + Matter.World.add(engine.world, this.springs); + } + + drawBlob() { + const minX = + Math.min(...this.nodes.map(n => n.position.x)) - 4 * BLOB_NODE_SIZE; + const maxX = + Math.max(...this.nodes.map(n => n.position.x)) + 4 * BLOB_NODE_SIZE; + const minY = + Math.min(...this.nodes.map(n => n.position.y)) - 4 * BLOB_NODE_SIZE; + const maxY = + Math.max(...this.nodes.map(n => n.position.y)) + 4 * BLOB_NODE_SIZE; + const x = (maxX + minX) / 2; + const y = (maxY + minY) / 2; + const w = maxX - minX; + const h = maxY - minY; + + this.nodes.forEach((node, i) => { + this.tex.pixels[i * 4 + 0] = map( + node.position.x, + minX, + maxX, + 0, + 255, + true + ); + this.tex.pixels[i * 4 + 1] = map( + node.position.y, + minY, + maxY, + 0, + 255, + true + ); + }); + this.tex.updatePixels(); + + push(); + translate(x, y); + noStroke(); + shader(metaballShader); + metaballShader.setUniform('bbox', [minX, minY, maxX, maxY]); + metaballShader.setUniform('k', BLOB_NODE_SIZE * 3); + metaballShader.setUniform('numNodes', this.nodes.length); + metaballShader.setUniform('data', this.tex); + metaballShader.setUniform('r', BLOB_NODE_R); // TODO: make this a public API // @ts-ignore - metaballShader.setUniform('c', this.c.array()) - metaballShader.setUniform('spheremap', spheremap) - plane(w, h) - pop() - } - - draw2D() { - fill(this.c) - stroke(this.c) - strokeWeight(2 * BLOB_NODE_R) - strokeJoin(ROUND) - const hull = convexHull(this.nodes.map(n => n.position)) - beginShape() - for (const { x, y } of hull) vertex(x, y) - endShape(CLOSE) - - noStroke() - fill(0) - for (const node of this.nodes) { - circle(node.position.x, node.position.y, BLOB_NODE_R * 2) - } - } + metaballShader.setUniform('c', this.c.array()); + metaballShader.setUniform('spheremap', spheremap); + plane(w, h); + pop(); + } + + draw2D() { + fill(this.c); + stroke(this.c); + strokeWeight(2 * BLOB_NODE_R); + strokeJoin(ROUND); + const hull = convexHull(this.nodes.map(n => n.position)); + beginShape(); + for (const { x, y } of hull) vertex(x, y); + endShape(CLOSE); + + noStroke(); + fill(0); + for (const node of this.nodes) { + circle(node.position.x, node.position.y, BLOB_NODE_R * 2); + } + } } let vert = `#version 300 es @@ -232,7 +265,7 @@ void main() { // Pass along data to the fragment shader vTexCoord = aTexCoord; -}` +}`; let frag = `#version 300 es precision highp float; @@ -310,39 +343,39 @@ void main() { // outColor = normal; fragColor = vec4(outColor, 1.) * (1. - smoothstep(0., 0.01, dist)); -}` +}`; const comparison = (a: p5.Vector, b: p5.Vector) => { - return a.x == b.x ? a.y - b.y : a.x - b.x -} + return a.x == b.x ? a.y - b.y : a.x - b.x; +}; const cross = (a: p5.Vector, b: p5.Vector, o: p5.Vector) => { - return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x) -} + return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x); +}; function convexHull(points: p5.Vector[]) { - points.sort(comparison) - const L: p5.Vector[] = [] + points.sort(comparison); + const L: p5.Vector[] = []; for (let i = 0; i < points.length; i++) { while ( L.length >= 2 && cross(L[L.length - 2], L[L.length - 1], points[i]) <= 0 ) { - L.pop() + L.pop(); } - L.push(points[i]) + L.push(points[i]); } - const U: p5.Vector[] = [] + const U: p5.Vector[] = []; for (let i = points.length - 1; i >= 0; i--) { while ( U.length >= 2 && cross(U[U.length - 2], U[U.length - 1], points[i]) <= 0 ) { - U.pop() + U.pop(); } - U.push(points[i]) + U.push(points[i]); } - L.pop() - U.pop() - return L.concat(U) + L.pop(); + U.pop(); + return L.concat(U); } diff --git a/test/types/webgpu.ts b/test/types/webgpu.ts index 7c9016cdf1..9c003cb892 100644 --- a/test/types/webgpu.ts +++ b/test/types/webgpu.ts @@ -1,4 +1,4 @@ -import p5 from '../../types/global' +import p5 from '../../types/global'; async function setup() { const renderer: p5.Renderer = await createCanvas(100, 100, WEBGPU); diff --git a/test/unit/accessibility/describe.js b/test/unit/accessibility/describe.js index 31676179ff..427aa16ce0 100644 --- a/test/unit/accessibility/describe.js +++ b/test/unit/accessibility/describe.js @@ -1,25 +1,25 @@ import { mockP5, mockP5Prototype } from '../../js/mocks'; import describe from '../../../src/accessibility/describe'; -suite('describe', function() { +suite('describe', function () { const myID = 'myCanvasID'; - beforeAll(function() { + beforeAll(function () { describe(mockP5, mockP5Prototype); mockP5Prototype.LABEL = 'label'; mockP5Prototype.FALLBACK = 'fallback'; }); - suite('p5.prototype.describe', function() { - test('should be a function', function() { + suite('p5.prototype.describe', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.describe); assert.typeOf(mockP5Prototype.describe, 'function'); }); - test('err when LABEL at param #0', function() { + test('err when LABEL at param #0', function () { assert.throws( - function() { + function () { mockP5Prototype.describe(mockP5Prototype.LABEL); }, Error, @@ -27,19 +27,19 @@ suite('describe', function() { ); }); - test('should create description as fallback', function() { + test('should create description as fallback', function () { mockP5Prototype.describe('a'); let actual = document.getElementById(myID + '_fallbackDesc'); assert.deepEqual(actual.innerHTML, 'a.'); }); - test('should not add extra period if string ends in "."', function() { + test('should not add extra period if string ends in "."', function () { mockP5Prototype.describe('A.'); let actual = document.getElementById(myID + '_fallbackDesc'); assert.deepEqual(actual.innerHTML, 'A.'); }); - test('should not add period if string ends in "!" or "?', function() { + test('should not add period if string ends in "!" or "?', function () { mockP5Prototype.describe('A!'); let actual = document.getElementById(myID + '_fallbackDesc'); if (actual.innerHTML === 'A!') { @@ -50,21 +50,21 @@ suite('describe', function() { } }); - test('should create description when called after describeElement()', function() { + test('should create description when called after describeElement()', function () { mockP5Prototype.describeElement('b', 'c'); mockP5Prototype.describe('a'); let actual = document.getElementById(myID + '_fallbackDesc'); assert.deepEqual(actual.innerHTML, 'a.'); }); - test('should create Label adjacent to canvas', function() { + test('should create Label adjacent to canvas', function () { mockP5Prototype.describe('a', mockP5Prototype.LABEL); let actual = document.getElementById(myID + '_labelDesc'); assert.deepEqual(actual.innerHTML, 'a.'); }); - test('should create Label adjacent to canvas when label of element already exists', function() { + test('should create Label adjacent to canvas when label of element already exists', function () { mockP5Prototype.describeElement('ba', 'c', mockP5Prototype.LABEL); mockP5Prototype.describe('a', mockP5Prototype.LABEL); let actual = document.getElementById(myID + '_labelDesc'); @@ -72,15 +72,15 @@ suite('describe', function() { }); }); - suite('p5.prototype.describeElement', function() { - test('should be a function', function() { + suite('p5.prototype.describeElement', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.describeElement); assert.typeOf(mockP5Prototype.describeElement, 'function'); }); - test('err when LABEL at param #0', function() { + test('err when LABEL at param #0', function () { assert.throws( - function() { + function () { mockP5Prototype.describeElement(mockP5Prototype.LABEL, 'b'); }, Error, @@ -88,9 +88,9 @@ suite('describe', function() { ); }); - test('err when LABEL at param #1', function() { + test('err when LABEL at param #1', function () { assert.throws( - function() { + function () { mockP5Prototype.describeElement('a', mockP5Prototype.LABEL); }, Error, @@ -98,19 +98,19 @@ suite('describe', function() { ); }); - test('should create element description as fallback', function() { + test('should create element description as fallback', function () { mockP5Prototype.describeElement('az', 'b'); let actual = document.getElementById(myID + '_fte_az').innerHTML; assert.deepEqual(actual, 'az:b.'); }); - test('should not add extra ":" if element name ends in colon', function() { + test('should not add extra ":" if element name ends in colon', function () { mockP5Prototype.describeElement('ab:', 'b.'); let actual = document.getElementById(myID + '_fte_ab').innerHTML; assert.deepEqual(actual, 'ab:b.'); }); - test('should replace ";", ",", "." for ":" in element name', function() { + test('should replace ";", ",", "." for ":" in element name', function () { let actual; mockP5Prototype.describeElement('ac;', 'b.'); if ( @@ -129,20 +129,20 @@ suite('describe', function() { } }); - test('should create element description when called after describe()', function() { + test('should create element description when called after describe()', function () { mockP5Prototype.describe('c'); mockP5Prototype.describeElement('af', 'b'); let actual = document.getElementById(myID + '_fte_af').innerHTML; assert.deepEqual(actual, 'af:b.'); }); - test('should create element label adjacent to canvas', function() { + test('should create element label adjacent to canvas', function () { mockP5Prototype.describeElement('ag', 'b', mockP5Prototype.LABEL); const actual = document.getElementById(myID + '_lte_ag').innerHTML; assert.deepEqual(actual, 'ag:b.'); }); - test('should create element label adjacent to canvas when called after describe()', function() { + test('should create element label adjacent to canvas when called after describe()', function () { mockP5Prototype.describe('c', mockP5Prototype.LABEL); mockP5Prototype.describeElement('ah:', 'b', mockP5Prototype.LABEL); const actual = document.getElementById(myID + '_lte_ah').innerHTML; diff --git a/test/unit/accessibility/outputs.js b/test/unit/accessibility/outputs.js index 167888bf1a..3da3291c6a 100644 --- a/test/unit/accessibility/outputs.js +++ b/test/unit/accessibility/outputs.js @@ -4,24 +4,24 @@ import textOutput from '../../../src/accessibility/textOutput'; import p5 from '../../../src/app.js'; // TODO: Is it possible to test this without a runtime? -suite('outputs', function() { +suite('outputs', function () { let myID = 'myCanvasID'; - beforeAll(function() { + beforeAll(function () { outputs(mockP5, mockP5Prototype); textOutput(mockP5, mockP5Prototype); }); - suite('p5.prototype.textOutput', function() { - test('should be a function', function() { + suite('p5.prototype.textOutput', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.textOutput); assert.typeOf(mockP5Prototype.textOutput, 'function'); }); - test('should not break for webgl', async function() { + test('should not break for webgl', async function () { await new Promise(res => { new p5(p => { - p.setup = function() { + p.setup = function () { p.createCanvas(50, 50, p.WEBGL); p.textOutput(); p.circle(0, 0, 20); @@ -31,10 +31,10 @@ suite('outputs', function() { }); }); - test('should not break for webgl', async function() { + test('should not break for webgl', async function () { await new Promise(res => { new p5(p => { - p.setup = function() { + p.setup = function () { p.createCanvas(50, 50, p.WEBGL); p.gridOutput(); p.circle(0, 0, 20); @@ -47,20 +47,21 @@ suite('outputs', function() { let expected = 'Your output is a, 100 by 100 pixels, white canvas containing the following shape:'; - test('should create output as fallback', function() { - return new Promise(function(resolve, reject) { + test('should create output as fallback', function () { + return new Promise(function (resolve, reject) { let actual = ''; - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { let cnv = p.createCanvas(100, 100); cnv.id('myCanvasID'); p.textOutput(); p.line(0, 0, 100, 100); }; - p.draw = function() { + p.draw = function () { if (p.frameCount === 1) { - actual = document.getElementById('myCanvasIDtextOutput_summary') - .innerHTML; + actual = document.getElementById( + 'myCanvasIDtextOutput_summary' + ).innerHTML; if (actual === expected) { resolve(); } else { @@ -73,24 +74,25 @@ suite('outputs', function() { }); }); - test('should create output as label', function() { - return new Promise(function(resolve, reject) { + test('should create output as label', function () { + return new Promise(function (resolve, reject) { let label = ''; let fallback = ''; - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { let cnv = p.createCanvas(100, 100); cnv.id('myCanvasID'); }; - p.draw = function() { + p.draw = function () { p.textOutput(p.LABEL); p.line(0, 0, 100, 100); if (p.frameCount === 2) { label = document.getElementById( 'myCanvasIDtextOutputLabel_summary' ).innerHTML; - fallback = document.getElementById('myCanvasIDtextOutput_summary') - .innerHTML; + fallback = document.getElementById( + 'myCanvasIDtextOutput_summary' + ).innerHTML; if (label === expected && fallback === expected) { resolve(); } else { @@ -103,22 +105,23 @@ suite('outputs', function() { }); }); - test('should create text output for arc()', function() { - return new Promise(function(resolve, reject) { + test('should create text output for arc()', function () { + return new Promise(function (resolve, reject) { expected = '
  4. red arc, at middle, covering 31% of the canvas.
  5. '; - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { let cnv = p.createCanvas(100, 100); cnv.id('myCanvasID'); }; - p.draw = function() { + p.draw = function () { p.textOutput(); p.fill(255, 0, 0); p.arc(50, 50, 80, 80, 0, p.PI + p.QUARTER_PI); if (p.frameCount === 2) { - let actual = document.getElementById('myCanvasIDtextOutput_list') - .innerHTML; + let actual = document.getElementById( + 'myCanvasIDtextOutput_list' + ).innerHTML; if (actual === expected) { resolve(); } else { @@ -131,22 +134,23 @@ suite('outputs', function() { }); }); - test('should create text output for ellipse()', function() { - return new Promise(function(resolve, reject) { + test('should create text output for ellipse()', function () { + return new Promise(function (resolve, reject) { expected = '
  6. green circle, at middle, covering 24% of the canvas.
  7. '; - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { let cnv = p.createCanvas(100, 100); cnv.id('myCanvasID'); p.textOutput(); p.fill(0, 255, 0); p.ellipse(56, 46, 55, 55); }; - p.draw = function() { + p.draw = function () { if (p.frameCount === 1) { - let actual = document.getElementById('myCanvasIDtextOutput_list') - .innerHTML; + let actual = document.getElementById( + 'myCanvasIDtextOutput_list' + ).innerHTML; if (actual === expected) { resolve(); } else { @@ -159,22 +163,23 @@ suite('outputs', function() { }); }); - test('should create text output for triangle()', function() { - return new Promise(function(resolve, reject) { + test('should create text output for triangle()', function () { + return new Promise(function (resolve, reject) { expected = '
  8. green triangle, at top left, covering 13% of the canvas.
  9. '; - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { let cnv = p.createCanvas(100, 100); cnv.id('myCanvasID'); p.textOutput(); p.fill(0, 255, 0); p.triangle(0, 0, 0, 50, 50, 0); }; - p.draw = function() { + p.draw = function () { if (p.frameCount === 1) { - let actual = document.getElementById('myCanvasIDtextOutput_list') - .innerHTML; + let actual = document.getElementById( + 'myCanvasIDtextOutput_list' + ).innerHTML; if (actual === expected) { resolve(); } else { @@ -188,8 +193,8 @@ suite('outputs', function() { }); }); - suite('p5.prototype.gridOutput', function() { - test('should be a function', function() { + suite('p5.prototype.gridOutput', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.gridOutput); assert.typeOf(mockP5Prototype.gridOutput, 'function'); }); @@ -197,20 +202,21 @@ suite('outputs', function() { let expected = 'white canvas, 100 by 100 pixels, contains 1 shape: 1 square'; - test('should create output as fallback', function() { - return new Promise(function(resolve, reject) { + test('should create output as fallback', function () { + return new Promise(function (resolve, reject) { let actual = ''; - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { let cnv = p.createCanvas(100, 100); cnv.id('myCanvasID'); p.gridOutput(); p.rect(0, 0, 100, 100); }; - p.draw = function() { + p.draw = function () { if (p.frameCount === 1) { - actual = document.getElementById('myCanvasIDgridOutput_summary') - .innerHTML; + actual = document.getElementById( + 'myCanvasIDgridOutput_summary' + ).innerHTML; if (actual === expected) { resolve(); } else { @@ -223,24 +229,25 @@ suite('outputs', function() { }); }); - test('should create output as label', function() { - return new Promise(function(resolve, reject) { + test('should create output as label', function () { + return new Promise(function (resolve, reject) { let label = ''; let fallback = ''; - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { let cnv = p.createCanvas(100, 100); cnv.id('myCanvasID'); }; - p.draw = function() { + p.draw = function () { p.gridOutput(p.LABEL); p.square(0, 0, 100, 100); if (p.frameCount === 2) { label = document.getElementById( 'myCanvasIDgridOutputLabel_summary' ).innerHTML; - fallback = document.getElementById('myCanvasIDgridOutput_summary') - .innerHTML; + fallback = document.getElementById( + 'myCanvasIDgridOutput_summary' + ).innerHTML; if (label === expected && fallback === expected) { resolve(); } else { @@ -253,21 +260,22 @@ suite('outputs', function() { }); }); - test('should create text output for quad()', function() { - return new Promise(function(resolve, reject) { + test('should create text output for quad()', function () { + return new Promise(function (resolve, reject) { expected = 'red quadrilateral, location = top left, area = 45 %'; - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { let cnv = p.createCanvas(100, 100); cnv.id('myCanvasID'); }; - p.draw = function() { + p.draw = function () { p.gridOutput(); p.fill(255, 0, 0); p.quad(0, 0, 80, 0, 50, 50, 0, 100); if (p.frameCount === 2) { - let actual = document.getElementById('myCanvasIDgridOutputshape0') - .innerHTML; + let actual = document.getElementById( + 'myCanvasIDgridOutputshape0' + ).innerHTML; if (actual === expected) { resolve(); } else { @@ -280,21 +288,22 @@ suite('outputs', function() { }); }); - test('should create text output for point()', function() { - return new Promise(function(resolve, reject) { + test('should create text output for point()', function () { + return new Promise(function (resolve, reject) { expected = 'dark fuchsia point, location = bottom right'; - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { let cnv = p.createCanvas(100, 100); cnv.id('myCanvasID'); p.gridOutput(); p.stroke('purple'); p.point(85, 75); }; - p.draw = function() { + p.draw = function () { if (p.frameCount === 1) { - let actual = document.getElementById('myCanvasIDgridOutputshape0') - .innerHTML; + let actual = document.getElementById( + 'myCanvasIDgridOutputshape0' + ).innerHTML; if (actual === expected) { resolve(); } else { @@ -307,21 +316,22 @@ suite('outputs', function() { }); }); - test('should create text output for triangle()', function() { - return new Promise(function(resolve, reject) { + test('should create text output for triangle()', function () { + return new Promise(function (resolve, reject) { expected = 'green triangle, location = top left, area = 13 %'; - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { let cnv = p.createCanvas(100, 100); cnv.id('myCanvasID'); p.gridOutput(); p.fill(0, 255, 0); p.triangle(0, 0, 0, 50, 50, 0); }; - p.draw = function() { + p.draw = function () { if (p.frameCount === 1) { - let actual = document.getElementById('myCanvasIDgridOutputshape0') - .innerHTML; + let actual = document.getElementById( + 'myCanvasIDgridOutputshape0' + ).innerHTML; if (actual === expected) { resolve(); } else { diff --git a/test/unit/assets/testFilter.js b/test/unit/assets/testFilter.js index d0aead583b..7a26459c77 100644 --- a/test/unit/assets/testFilter.js +++ b/test/unit/assets/testFilter.js @@ -3,4 +3,4 @@ getColor((inputs, canvasContent) => { const originalColor = getTexture(canvasContent, inputs.texCoord); // Swap red and green channels, keep blue and alpha return [originalColor.g, originalColor.r, originalColor.b, originalColor.a]; -}); \ No newline at end of file +}); diff --git a/test/unit/assets/testMaterial.js b/test/unit/assets/testMaterial.js index 7c0f32e6b9..6d642922a9 100644 --- a/test/unit/assets/testMaterial.js +++ b/test/unit/assets/testMaterial.js @@ -1,9 +1,9 @@ // Test material shader that creates a gradient based on position -getPixelInputs((inputs) => { +getPixelInputs(inputs => { // Create a color gradient based on texture coordinates const red = inputs.texCoord.x; const green = inputs.texCoord.y; const blue = 0.5; inputs.color = [red, green, blue, 1.0]; return inputs; -}); \ No newline at end of file +}); diff --git a/test/unit/color/color_conversion.js b/test/unit/color/color_conversion.js index 777ddee526..1e3a542e0a 100644 --- a/test/unit/color/color_conversion.js +++ b/test/unit/color/color_conversion.js @@ -1,13 +1,13 @@ import p5 from '../../../src/app.js'; -assert.arrayApproximately = function(arr1, arr2, delta) { +assert.arrayApproximately = function (arr1, arr2, delta) { assert.equal(arr1.length, arr2.length); - for(var i = 0; i < arr1.length; i++) { + for (var i = 0; i < arr1.length; i++) { assert.approximately(arr1[i], arr2[i], delta); } }; -suite.todo('color/p5.ColorConversion', function() { +suite.todo('color/p5.ColorConversion', function () { var rgba = [1, 0, 0.4, 0.8]; var rgbaWithMaxHue = [1, 0, 0, 0.6]; var rgbaWithHighLightness = [0.969, 0.753, 0.122, 0.8]; @@ -18,58 +18,58 @@ suite.todo('color/p5.ColorConversion', function() { var accuracy = 0.01; var result; - suite('rgbaToHSBA', function() { - test('rgba converts to hsba', function() { + suite('rgbaToHSBA', function () { + test('rgba converts to hsba', function () { result = p5.ColorConversion._rgbaToHSBA(rgba); assert.arrayApproximately(result, hsba, accuracy); }); }); - suite('hsbaToRGBA', function() { - test('hsba converts to rgba', function() { + suite('hsbaToRGBA', function () { + test('hsba converts to rgba', function () { result = p5.ColorConversion._hsbaToRGBA(hsba); assert.arrayApproximately(result, rgba, accuracy); }); - test('handles maximum hue value', function() { + test('handles maximum hue value', function () { result = p5.ColorConversion._hsbaToRGBA(hsbaWithMaxHue); assert.arrayApproximately(result, rgbaWithMaxHue, accuracy); }); }); - suite('hslaToRGBA', function() { - test('hsla converts to rgba', function() { + suite('hslaToRGBA', function () { + test('hsla converts to rgba', function () { result = p5.ColorConversion._hslaToRGBA(hsla); assert.arrayApproximately(result, rgba, accuracy); }); - test('handles maximum hue value', function() { + test('handles maximum hue value', function () { result = p5.ColorConversion._hslaToRGBA(hslaWithMaxHue); assert.arrayApproximately(result, rgbaWithMaxHue, accuracy); }); }); - suite('rgbaToHSLA', function() { - test('rgba converts to hsla (low lightness)', function() { + suite('rgbaToHSLA', function () { + test('rgba converts to hsla (low lightness)', function () { result = p5.ColorConversion._rgbaToHSLA(rgba); assert.arrayApproximately(result, hsla, accuracy); }); - test('rgba converts to hsla (high lightness)', function() { + test('rgba converts to hsla (high lightness)', function () { result = p5.ColorConversion._rgbaToHSLA(rgbaWithHighLightness); assert.arrayApproximately(result, [0.12, 0.93, 0.55, 0.8], accuracy); }); }); - suite('hslaToHSBA', function() { - test('hsla converts to hsba', function() { + suite('hslaToHSBA', function () { + test('hsla converts to hsba', function () { result = p5.ColorConversion._hslaToHSBA(hsla); assert.arrayApproximately(result, hsba, accuracy); }); }); - suite('hsbaToHSLA', function() { - test('hsba converts to hsla', function() { + suite('hsbaToHSLA', function () { + test('hsba converts to hsla', function () { result = p5.ColorConversion._hsbaToHSLA(hsba); assert.arrayApproximately(result, hsla, accuracy); }); diff --git a/test/unit/color/creating_reading.js b/test/unit/color/creating_reading.js index 31d7379802..50ca86501f 100644 --- a/test/unit/color/creating_reading.js +++ b/test/unit/color/creating_reading.js @@ -3,8 +3,8 @@ import creatingReading from '../../../src/color/creating_reading'; import setting from '../../../src/color/setting'; import p5Color from '../../../src/color/p5.Color'; -suite('color/CreatingReading', function() { - beforeAll(async function() { +suite('color/CreatingReading', function () { + beforeAll(async function () { creatingReading(mockP5, mockP5Prototype); setting(mockP5, mockP5Prototype); p5Color(mockP5, mockP5Prototype, {}); @@ -13,51 +13,54 @@ suite('color/CreatingReading', function() { var fromColor; var toColor; - suite.todo('p5.prototype.alpha', function() { - beforeEach(function() { + suite.todo('p5.prototype.alpha', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.RGB); }); }); - suite.todo('p5.prototype.red, green, blue', function() { - beforeEach(function() { + suite.todo('p5.prototype.red, green, blue', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.RGB); }); }); - suite.todo('p5.prototype.hue, brightness, lightness, saturation', function() { - beforeEach(function() { - mockP5Prototype.colorMode(mockP5Prototype.HSL); - }); - }); - - suite('constructor clamping', function() { - test('should work on multi channels', function() { + suite.todo( + 'p5.prototype.hue, brightness, lightness, saturation', + function () { + beforeEach(function () { + mockP5Prototype.colorMode(mockP5Prototype.HSL); + }); + } + ); + + suite('constructor clamping', function () { + test('should work on multi channels', function () { const myColor = mockP5Prototype.color(1000, 1000, 1000, 1000); assert.deepEqual(myColor.array(), [1, 1, 1, 1]); }); - test('should work on gray + alpha', function() { + test('should work on gray + alpha', function () { const myColor = mockP5Prototype.color(1000, 1000); assert.deepEqual(myColor.array(), [1, 1, 1, 1]); }); - test('should work on gray', function() { + test('should work on gray', function () { const myColor = mockP5Prototype.color(1000); assert.deepEqual(myColor.array(), [1, 1, 1, 1]); }); - test('normal values work', function() { + test('normal values work', function () { const myColor = mockP5Prototype.color(255 / 2); assert.deepEqual(myColor.array(), [0.5, 0.5, 0.5, 1]); }); }); - suite('p5.prototype.lerpColor', function() { - beforeEach(function() { + suite('p5.prototype.lerpColor', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.RGB); fromColor = mockP5Prototype.color(218, 165, 32); toColor = mockP5Prototype.color(72, 61, 139); }); - test('should correctly get lerp colors in RGB', function() { + test('should correctly get lerp colors in RGB', function () { var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33); var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66); @@ -70,7 +73,7 @@ suite('color/CreatingReading', function() { assert.closeTo(interB._color.coords[2] * 255, 103, 1); }); - test('should correctly get lerp colors in HSL', function() { + test('should correctly get lerp colors in HSL', function () { // NOTE: This is equivalent to RGB case so is testing nothing new mockP5Prototype.colorMode(mockP5Prototype.HSL); var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33); @@ -85,7 +88,7 @@ suite('color/CreatingReading', function() { assert.closeTo(interB._color.coords[2], 43, 1); }); - test('should correctly get lerp colors in HSB', function() { + test('should correctly get lerp colors in HSB', function () { // NOTE: This is equivalent to RGB case so is testing nothing new mockP5Prototype.colorMode(mockP5Prototype.HSB); var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33); @@ -100,7 +103,7 @@ suite('color/CreatingReading', function() { assert.closeTo(interB._color.coords[2], 47, 1); }); - test.todo('should not extrapolate', function() { + test.todo('should not extrapolate', function () { // NOTE: maybe it should extrapolate var interA = mockP5Prototype.lerpColor(fromColor, toColor, -0.5); var interB = mockP5Prototype.lerpColor(fromColor, toColor, 1.5); @@ -109,14 +112,14 @@ suite('color/CreatingReading', function() { }); }); - suite('p5.prototype.lerpColor with alpha', function() { - beforeEach(function() { + suite('p5.prototype.lerpColor with alpha', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.RGB); fromColor = mockP5Prototype.color(218, 165, 32, 49); toColor = mockP5Prototype.color(72, 61, 139, 200); }); - test('should correctly get lerp colors in RGB with alpha', function() { + test('should correctly get lerp colors in RGB with alpha', function () { var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33); var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66); @@ -127,11 +130,11 @@ suite('color/CreatingReading', function() { assert.closeTo(interB._color.coords[0], 0.47, 0.01); assert.closeTo(interB._color.coords[1], 0.37, 0.01); - assert.closeTo(interB._color.coords[2], 0.40, 0.01); + assert.closeTo(interB._color.coords[2], 0.4, 0.01); assert.closeTo(interB._color.alpha, 0.58, 0.01); }); - test('should correctly get lerp colors in HSL with alpha', function() { + test('should correctly get lerp colors in HSL with alpha', function () { mockP5Prototype.colorMode(mockP5Prototype.HSL); var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33); var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66); @@ -147,7 +150,7 @@ suite('color/CreatingReading', function() { assert.closeTo(interB._color.alpha, 0.58, 0.01); }); - test('should correctly get lerp colors in HSB with alpha', function() { + test('should correctly get lerp colors in HSB with alpha', function () { mockP5Prototype.colorMode(mockP5Prototype.HSB); var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33); var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66); @@ -163,7 +166,7 @@ suite('color/CreatingReading', function() { assert.closeTo(interB._color.alpha, 0.58, 0.01); }); - test.todo('should not extrapolate', function() { + test.todo('should not extrapolate', function () { // NOTE: maybe it should extrapolate var interA = mockP5Prototype.lerpColor(fromColor, toColor, -0.5); var interB = mockP5Prototype.lerpColor(fromColor, toColor, 1.5); diff --git a/test/unit/color/hsb.js b/test/unit/color/hsb.js index 7761c9594e..e536cc78d2 100644 --- a/test/unit/color/hsb.js +++ b/test/unit/color/hsb.js @@ -15,26 +15,26 @@ import HSBSpace from '../../../src/color/color_spaces/hsb.js'; -suite('color/HSB Color Space', function() { +suite('color/HSB Color Space', function () { const accuracy = 0.01; - suite('HSB to RGB conversion (toBase)', function() { - suite('Primary Colors', function() { - test('pure red: HSB(0, 100, 100) → RGB(1, 0, 0)', function() { + suite('HSB to RGB conversion (toBase)', function () { + suite('Primary Colors', function () { + test('pure red: HSB(0, 100, 100) → RGB(1, 0, 0)', function () { const result = HSBSpace.toBase([0, 100, 100]); assert.approximately(result[0], 1, accuracy); assert.approximately(result[1], 0, accuracy); assert.approximately(result[2], 0, accuracy); }); - test('pure green: HSB(120, 100, 100) → RGB(0, 1, 0)', function() { + test('pure green: HSB(120, 100, 100) → RGB(0, 1, 0)', function () { const result = HSBSpace.toBase([120, 100, 100]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 1, accuracy); assert.approximately(result[2], 0, accuracy); }); - test('pure blue: HSB(240, 100, 100) → RGB(0, 0, 1)', function() { + test('pure blue: HSB(240, 100, 100) → RGB(0, 0, 1)', function () { const result = HSBSpace.toBase([240, 100, 100]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 0, accuracy); @@ -42,22 +42,22 @@ suite('color/HSB Color Space', function() { }); }); - suite('Secondary Colors', function() { - test('yellow: HSB(60, 100, 100) → RGB(1, 1, 0)', function() { + suite('Secondary Colors', function () { + test('yellow: HSB(60, 100, 100) → RGB(1, 1, 0)', function () { const result = HSBSpace.toBase([60, 100, 100]); assert.approximately(result[0], 1, accuracy); assert.approximately(result[1], 1, accuracy); assert.approximately(result[2], 0, accuracy); }); - test('cyan: HSB(180, 100, 100) → RGB(0, 1, 1)', function() { + test('cyan: HSB(180, 100, 100) → RGB(0, 1, 1)', function () { const result = HSBSpace.toBase([180, 100, 100]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 1, accuracy); assert.approximately(result[2], 1, accuracy); }); - test('magenta: HSB(300, 100, 100) → RGB(1, 0, 1)', function() { + test('magenta: HSB(300, 100, 100) → RGB(1, 0, 1)', function () { const result = HSBSpace.toBase([300, 100, 100]); assert.approximately(result[0], 1, accuracy); assert.approximately(result[1], 0, accuracy); @@ -65,29 +65,29 @@ suite('color/HSB Color Space', function() { }); }); - suite('Grayscale (Saturation = 0)', function() { - test('white: HSB(0, 0, 100) → RGB(1, 1, 1)', function() { + suite('Grayscale (Saturation = 0)', function () { + test('white: HSB(0, 0, 100) → RGB(1, 1, 1)', function () { const result = HSBSpace.toBase([0, 0, 100]); assert.approximately(result[0], 1, accuracy); assert.approximately(result[1], 1, accuracy); assert.approximately(result[2], 1, accuracy); }); - test('black: HSB(0, 0, 0) → RGB(0, 0, 0)', function() { + test('black: HSB(0, 0, 0) → RGB(0, 0, 0)', function () { const result = HSBSpace.toBase([0, 0, 0]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 0, accuracy); assert.approximately(result[2], 0, accuracy); }); - test('mid gray: HSB(0, 0, 50) → RGB(0.5, 0.5, 0.5)', function() { + test('mid gray: HSB(0, 0, 50) → RGB(0.5, 0.5, 0.5)', function () { const result = HSBSpace.toBase([0, 0, 50]); assert.approximately(result[0], 0.5, accuracy); assert.approximately(result[1], 0.5, accuracy); assert.approximately(result[2], 0.5, accuracy); }); - test('grayscale ignores hue: HSB(180, 0, 50) → RGB(0.5, 0.5, 0.5)', function() { + test('grayscale ignores hue: HSB(180, 0, 50) → RGB(0.5, 0.5, 0.5)', function () { const result = HSBSpace.toBase([180, 0, 50]); assert.approximately(result[0], 0.5, accuracy); assert.approximately(result[1], 0.5, accuracy); @@ -95,15 +95,15 @@ suite('color/HSB Color Space', function() { }); }); - suite('Partial Saturation', function() { - test('50% saturation red: HSB(0, 50, 100) → RGB(1, 0.5, 0.5)', function() { + suite('Partial Saturation', function () { + test('50% saturation red: HSB(0, 50, 100) → RGB(1, 0.5, 0.5)', function () { const result = HSBSpace.toBase([0, 50, 100]); assert.approximately(result[0], 1, accuracy); assert.approximately(result[1], 0.5, accuracy); assert.approximately(result[2], 0.5, accuracy); }); - test('50% brightness red: HSB(0, 100, 50) → RGB(0.5, 0, 0)', function() { + test('50% brightness red: HSB(0, 100, 50) → RGB(0.5, 0, 0)', function () { const result = HSBSpace.toBase([0, 100, 50]); assert.approximately(result[0], 0.5, accuracy); assert.approximately(result[1], 0, accuracy); @@ -111,15 +111,15 @@ suite('color/HSB Color Space', function() { }); }); - suite('Edge Cases', function() { - test('hue at 360 should be same as 0 (red)', function() { + suite('Edge Cases', function () { + test('hue at 360 should be same as 0 (red)', function () { const result = HSBSpace.toBase([360, 100, 100]); assert.approximately(result[0], 1, accuracy); assert.approximately(result[1], 0, accuracy); assert.approximately(result[2], 0, accuracy); }); - test('brightness 0 produces black regardless of hue/saturation', function() { + test('brightness 0 produces black regardless of hue/saturation', function () { const result = HSBSpace.toBase([120, 100, 0]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 0, accuracy); @@ -128,23 +128,23 @@ suite('color/HSB Color Space', function() { }); }); - suite('RGB to HSB conversion (fromBase)', function() { - suite('Primary Colors', function() { - test('pure red: RGB(1, 0, 0) → HSB(0, 100, 100)', function() { + suite('RGB to HSB conversion (fromBase)', function () { + suite('Primary Colors', function () { + test('pure red: RGB(1, 0, 0) → HSB(0, 100, 100)', function () { const result = HSBSpace.fromBase([1, 0, 0]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 100, accuracy); assert.approximately(result[2], 100, accuracy); }); - test('pure green: RGB(0, 1, 0) → HSB(120, 100, 100)', function() { + test('pure green: RGB(0, 1, 0) → HSB(120, 100, 100)', function () { const result = HSBSpace.fromBase([0, 1, 0]); assert.approximately(result[0], 120, accuracy); assert.approximately(result[1], 100, accuracy); assert.approximately(result[2], 100, accuracy); }); - test('pure blue: RGB(0, 0, 1) → HSB(240, 100, 100)', function() { + test('pure blue: RGB(0, 0, 1) → HSB(240, 100, 100)', function () { const result = HSBSpace.fromBase([0, 0, 1]); assert.approximately(result[0], 240, accuracy); assert.approximately(result[1], 100, accuracy); @@ -152,22 +152,22 @@ suite('color/HSB Color Space', function() { }); }); - suite('Secondary Colors', function() { - test('yellow: RGB(1, 1, 0) → HSB(60, 100, 100)', function() { + suite('Secondary Colors', function () { + test('yellow: RGB(1, 1, 0) → HSB(60, 100, 100)', function () { const result = HSBSpace.fromBase([1, 1, 0]); assert.approximately(result[0], 60, accuracy); assert.approximately(result[1], 100, accuracy); assert.approximately(result[2], 100, accuracy); }); - test('cyan: RGB(0, 1, 1) → HSB(180, 100, 100)', function() { + test('cyan: RGB(0, 1, 1) → HSB(180, 100, 100)', function () { const result = HSBSpace.fromBase([0, 1, 1]); assert.approximately(result[0], 180, accuracy); assert.approximately(result[1], 100, accuracy); assert.approximately(result[2], 100, accuracy); }); - test('magenta: RGB(1, 0, 1) → HSB(300, 100, 100)', function() { + test('magenta: RGB(1, 0, 1) → HSB(300, 100, 100)', function () { const result = HSBSpace.fromBase([1, 0, 1]); assert.approximately(result[0], 300, accuracy); assert.approximately(result[1], 100, accuracy); @@ -175,22 +175,22 @@ suite('color/HSB Color Space', function() { }); }); - suite('Grayscale', function() { - test('white: RGB(1, 1, 1) → HSB(0, 0, 100)', function() { + suite('Grayscale', function () { + test('white: RGB(1, 1, 1) → HSB(0, 0, 100)', function () { const result = HSBSpace.fromBase([1, 1, 1]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 0, accuracy); assert.approximately(result[2], 100, accuracy); }); - test('black: RGB(0, 0, 0) → HSB(0, 0, 0)', function() { + test('black: RGB(0, 0, 0) → HSB(0, 0, 0)', function () { const result = HSBSpace.fromBase([0, 0, 0]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 0, accuracy); assert.approximately(result[2], 0, accuracy); }); - test('mid gray: RGB(0.5, 0.5, 0.5) → HSB(0, 0, 50)', function() { + test('mid gray: RGB(0.5, 0.5, 0.5) → HSB(0, 0, 50)', function () { const result = HSBSpace.fromBase([0.5, 0.5, 0.5]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 0, accuracy); @@ -198,15 +198,15 @@ suite('color/HSB Color Space', function() { }); }); - suite('Partial Values', function() { - test('dark red: RGB(0.5, 0, 0) → HSB(0, 100, 50)', function() { + suite('Partial Values', function () { + test('dark red: RGB(0.5, 0, 0) → HSB(0, 100, 50)', function () { const result = HSBSpace.fromBase([0.5, 0, 0]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 100, accuracy); assert.approximately(result[2], 50, accuracy); }); - test('light red (pink): RGB(1, 0.5, 0.5) → HSB(0, 50, 100)', function() { + test('light red (pink): RGB(1, 0.5, 0.5) → HSB(0, 50, 100)', function () { const result = HSBSpace.fromBase([1, 0.5, 0.5]); assert.approximately(result[0], 0, accuracy); assert.approximately(result[1], 50, accuracy); @@ -215,8 +215,8 @@ suite('color/HSB Color Space', function() { }); }); - suite('Round-trip conversion', function() { - test('HSB → RGB → HSB should preserve values', function() { + suite('Round-trip conversion', function () { + test('HSB → RGB → HSB should preserve values', function () { const originalHSB = [210, 75, 80]; const rgb = HSBSpace.toBase(originalHSB); const resultHSB = HSBSpace.fromBase(rgb); @@ -226,7 +226,7 @@ suite('color/HSB Color Space', function() { assert.approximately(resultHSB[2], originalHSB[2], 0.1); }); - test('RGB → HSB → RGB should preserve values', function() { + test('RGB → HSB → RGB should preserve values', function () { const originalRGB = [0.7, 0.3, 0.5]; const hsb = HSBSpace.fromBase(originalRGB); const resultRGB = HSBSpace.toBase(hsb); diff --git a/test/unit/color/p5.Color.js b/test/unit/color/p5.Color.js index 73604d595a..5021a53f18 100644 --- a/test/unit/color/p5.Color.js +++ b/test/unit/color/p5.Color.js @@ -4,7 +4,7 @@ import setting from '../../../src/color/setting'; import { Color } from '../../../src/color/p5.Color'; import p5Color from '../../../src/color/p5.Color'; -suite('p5.Color', function() { +suite('p5.Color', function () { beforeAll(() => { creatingReading(mockP5, mockP5Prototype); setting(mockP5, mockP5Prototype); @@ -13,28 +13,28 @@ suite('p5.Color', function() { var c; - suite('p5.prototype.color(r,g,b)', function() { - beforeEach(function() { + suite('p5.prototype.color(r,g,b)', function () { + beforeEach(function () { c = mockP5Prototype.color(255, 0, 102); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 1); }); }); - suite('p5.prototype.color(r,g,b,a)', function() { - beforeEach(function() { + suite('p5.prototype.color(r,g,b,a)', function () { + beforeEach(function () { c = mockP5Prototype.color(255, 0, 102, 204); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 0.8); }); @@ -42,131 +42,134 @@ suite('p5.Color', function() { // NOTE: suite for all signatures - suite('p5.prototype.color(string)', function(){ - suite('#rgb', function(){ - beforeEach(function() { + suite('p5.prototype.color(string)', function () { + suite('#rgb', function () { + beforeEach(function () { c = mockP5Prototype.color('#f06'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 1); }); }); - suite('#rgba', function(){ - beforeEach(function() { + suite('#rgba', function () { + beforeEach(function () { c = mockP5Prototype.color('#f066'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 0.4); }); }); - suite('#rrggbb', function(){ - beforeEach(function() { + suite('#rrggbb', function () { + beforeEach(function () { c = mockP5Prototype.color('#ff0066'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 1); }); }); - suite('#rrggbbaa', function(){ - beforeEach(function() { + suite('#rrggbbaa', function () { + beforeEach(function () { c = mockP5Prototype.color('#f01dab1e'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { - assert.deepEqual(c._color.coords, [240, 29, 171].map(c => c/255)); - assert.equal(c._color.alpha, 30/255); + test('should correctly set RGBA property', function () { + assert.deepEqual( + c._color.coords, + [240, 29, 171].map(c => c / 255) + ); + assert.equal(c._color.alpha, 30 / 255); }); }); - suite('rgb(r,g,b)', function(){ - beforeEach(function() { + suite('rgb(r,g,b)', function () { + beforeEach(function () { c = mockP5Prototype.color('rgb(255,0,102)'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 1); }); }); - suite('rgb(r%,g%,b%)', function(){ - beforeEach(function() { + suite('rgb(r%,g%,b%)', function () { + beforeEach(function () { c = mockP5Prototype.color('rgb(100%, 0%, 40%)'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 1); }); }); - suite('rgba(r,g,b,a)', function(){ - beforeEach(function() { + suite('rgba(r,g,b,a)', function () { + beforeEach(function () { c = mockP5Prototype.color('rgba(255,0,102,0.8)'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 0.8); }); }); - suite('rgba(r%,g%,b%,a)', function(){ - beforeEach(function() { + suite('rgba(r%,g%,b%,a)', function () { + beforeEach(function () { c = mockP5Prototype.color('rgba(100.0%,0.0%,40%,0.8)'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 0.8); }); }); - suite('hsl(h, s%, l%)', function(){ - beforeEach(function() { + suite('hsl(h, s%, l%)', function () { + beforeEach(function () { c = mockP5Prototype.color('hsl(336, 100%, 50%)'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version // assert.deepEqual(c.color.coords, [336, 100, 50]); assert.equal(+c._color.coords[0], 336); @@ -176,14 +179,14 @@ suite('p5.Color', function() { }); }); - suite('hsla(h, s%, l%, a)', function() { - beforeEach(function() { + suite('hsla(h, s%, l%, a)', function () { + beforeEach(function () { c = mockP5Prototype.color('hsla(336, 100%, 50%, 0.8)'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version // assert.deepEqual(c.color.coords, [336, 100, 50]); assert.equal(+c._color.coords[0], 336); @@ -193,14 +196,14 @@ suite('p5.Color', function() { }); }); - suite('hsb(h, s%, b%)', function() { - beforeEach(function() { + suite('hsb(h, s%, b%)', function () { + beforeEach(function () { c = mockP5Prototype.color('hsb(336, 100%, 100%)'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version // assert.deepEqual(c.color.coords, [336, 100, 100]); assert.equal(+c._color.coords[0], 336); @@ -210,14 +213,14 @@ suite('p5.Color', function() { }); }); - suite('hsba(h, s%, b%, a)', function() { - beforeEach(function() { + suite('hsba(h, s%, b%, a)', function () { + beforeEach(function () { c = mockP5Prototype.color('hsba(336, 100%, 100%, 0.8)'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version // assert.deepEqual(c.color.coords, [336, 100, 50]); assert.equal(+c._color.coords[0], 336); @@ -227,17 +230,20 @@ suite('p5.Color', function() { }); }); - suite('named colors', function() { - beforeEach(function() { + suite('named colors', function () { + beforeEach(function () { c = mockP5Prototype.color('papayawhip'); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { - assert.deepEqual(c._color.coords, [255, 239, 213].map(c => c/255)); + test('should correctly set RGBA property', function () { + assert.deepEqual( + c._color.coords, + [255, 239, 213].map(c => c / 255) + ); assert.equal(c._color.alpha, 1); }); }); @@ -246,121 +252,121 @@ suite('p5.Color', function() { suite.todo('invalid string'); }); - suite('p5.prototype.color([])', function() { - beforeEach(function() { + suite('p5.prototype.color([])', function () { + beforeEach(function () { c = mockP5Prototype.color([255, 0, 102]); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 1); }); }); // color level setters - suite('in default mode', function() { - test('can be modified with alpha setter', function() { + suite('in default mode', function () { + test('can be modified with alpha setter', function () { let cc = mockP5Prototype.color(255, 0, 102, 204); assert.deepEqual(cc._color.coords, [1, 0, 0.4]); assert.equal(cc._color.alpha, 0.8); - cc.setAlpha(98/255); + cc.setAlpha(98 / 255); assert.deepEqual(cc._color.coords, [1, 0, 0.4]); - assert.equal(cc._color.alpha, 98/255); + assert.equal(cc._color.alpha, 98 / 255); }); - test('can be modified with rgb setters', function() { + test('can be modified with rgb setters', function () { var cc = mockP5Prototype.color(255, 0, 102, 204); assert.deepEqual(cc._color.coords, [1, 0, 0.4]); assert.equal(cc._color.alpha, 0.8); - cc.setRed(98/255); - assert.deepEqual(cc._color.coords, [98/255, 0, 0.4]); + cc.setRed(98 / 255); + assert.deepEqual(cc._color.coords, [98 / 255, 0, 0.4]); assert.equal(cc._color.alpha, 0.8); - cc.setGreen(44/255); - assert.deepEqual(cc._color.coords, [98/255, 44/255, 0.4]); + cc.setGreen(44 / 255); + assert.deepEqual(cc._color.coords, [98 / 255, 44 / 255, 0.4]); assert.equal(cc._color.alpha, 0.8); - cc.setBlue(244/255); - assert.deepEqual(cc._color.coords, [98/255, 44/255, 244/255]); + cc.setBlue(244 / 255); + assert.deepEqual(cc._color.coords, [98 / 255, 44 / 255, 244 / 255]); assert.equal(cc._color.alpha, 0.8); }); }); // Color Mode - suite('p5.Color in RGB mode with custom range', function() { - beforeEach(function() { + suite('p5.Color in RGB mode with custom range', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.RGB, 1); c = mockP5Prototype.color(1, 0, 0.4, 0.8); }); - test('should correctly convert to RGBA', function() { + test('should correctly convert to RGBA', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 0.8); }); - test('should correctly get RGBA property', function() { + test('should correctly get RGBA property', function () { assert.equal(c._getRed(), 1); assert.equal(c._getGreen(), 0); assert.equal(c._getBlue(), 0.4); assert.approximately(c._getAlpha(), 0.8, 0.05); }); - test('should correctly render color string', function() { + test('should correctly render color string', function () { assert.equal(c.toString(), '#ff0066cc'); }); - test('should correctly get RGBA property after overwrite', function() { + test('should correctly get RGBA property after overwrite', function () { mockP5Prototype.colorMode(mockP5Prototype.RGB, 255, 255, 255, 255); - assert.equal(c._getRed(), 255/255); - assert.equal(c._getGreen(), 0/255); - assert.equal(c._getBlue(), 102/255); - assert.equal(c._getAlpha(), 204/255); + assert.equal(c._getRed(), 255 / 255); + assert.equal(c._getGreen(), 0 / 255); + assert.equal(c._getBlue(), 102 / 255); + assert.equal(c._getAlpha(), 204 / 255); }); }); - suite('p5.Color in HSL mode', function() { - beforeEach(function() { + suite('p5.Color in HSL mode', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSL); c = mockP5Prototype.color(336, 100, 50); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [336, 100, 50]); assert.equal(c._color.alpha, 1); }); - test('can be modified with alpha setter', function() { + test('can be modified with alpha setter', function () { let cc = mockP5Prototype.color(336, 100, 50); cc.setAlpha(0.73); assert.deepEqual(cc._color.coords, [336, 100, 50]); assert.equal(cc._color.alpha, 0.73); }); - test('can be modified with rgb setters', function() { + test('can be modified with rgb setters', function () { let cc = mockP5Prototype.color(336, 100, 50); assert.deepEqual(cc._color.coords, [336, 100, 50]); assert.equal(cc._color.alpha, 1); // TODO: separately check these values are correct (not in test here) - cc.setRed(98/255); + cc.setRed(98 / 255); assert.closeTo(cc._color.coords[0], 297, 1); assert.closeTo(cc._color.coords[1], 100, 1); assert.closeTo(cc._color.coords[2], 20, 1); assert.equal(cc._color.alpha, 1); - cc.setGreen(44/255); + cc.setGreen(44 / 255); assert.closeTo(cc._color.coords[0], 295, 1); assert.closeTo(cc._color.coords[1], 39, 1); assert.closeTo(cc._color.coords[2], 28, 1); assert.equal(cc._color.alpha, 1); - cc.setBlue(244/255); + cc.setBlue(244 / 255); assert.closeTo(cc._color.coords[0], 256, 1); assert.closeTo(cc._color.coords[1], 90, 1); assert.closeTo(cc._color.coords[2], 56, 1); @@ -368,22 +374,22 @@ suite('p5.Color', function() { }); }); - suite('p5.Color in HSL mode with Alpha', function() { - beforeEach(function() { + suite('p5.Color in HSL mode with Alpha', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSL); c = mockP5Prototype.color(336, 100, 50, 0.8); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [336, 100, 50]); assert.equal(c._color.alpha, 0.8); }); - test('should correctly get hue/saturation/lightness/alpha', function() { + test('should correctly get hue/saturation/lightness/alpha', function () { assert.approximately(c._getHue(), 336, 0.5); assert.approximately(c._getSaturation(), 100, 0.5); assert.approximately(c._getLightness(), 50, 0.5); @@ -391,31 +397,31 @@ suite('p5.Color', function() { }); }); - suite('p5.Color in HSL mode with custom range', function() { - beforeEach(function() { + suite('p5.Color in HSL mode with custom range', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSL, 100, 200, 300, 10); c = mockP5Prototype.color(93.33, 200, 150, 8); }); - test('should correctly get HSLA property', function() { + test('should correctly get HSLA property', function () { assert.approximately(c._getHue(), 336, 0.5); assert.approximately(c._getSaturation(), 100, 0.5); assert.approximately(c._getLightness(), 50, 0.5); assert.approximately(c._getAlpha(), 0.8, 0.5); }); - test('should correctly convert to RGBA', function() { + test('should correctly convert to RGBA', function () { assert.closeTo(c._color.coords[0], 336, 1); assert.equal(c._color.coords[1], 100); assert.equal(c._color.coords[2], 50); assert.equal(c._color.alpha, 0.8); }); - test('should correctly render color string', function() { + test('should correctly render color string', function () { assert.equal(c.toString(), 'hsl(335.99 100% 50% / 0.8)'); }); - test('can be modified with alpha setter', function() { + test('can be modified with alpha setter', function () { let cc = mockP5Prototype.color(93.33, 200, 150, 8); cc.setAlpha(0.73); assert.closeTo(cc._color.coords[0], 336, 1); @@ -424,26 +430,26 @@ suite('p5.Color', function() { assert.equal(cc._color.alpha, 0.73); }); - test('can be modified with rgb setters', function() { + test('can be modified with rgb setters', function () { let cc = mockP5Prototype.color(93.33, 200, 150, 8); assert.closeTo(c._color.coords[0], 336, 1); assert.equal(c._color.coords[1], 100); assert.equal(c._color.coords[2], 50); assert.equal(c._color.alpha, 0.8); - cc.setRed(98/255); + cc.setRed(98 / 255); assert.closeTo(cc._color.coords[0], 297, 1); assert.closeTo(cc._color.coords[1], 100, 1); assert.closeTo(cc._color.coords[2], 20, 1); assert.equal(cc._color.alpha, 0.8); - cc.setGreen(44/255); + cc.setGreen(44 / 255); assert.closeTo(cc._color.coords[0], 295, 1); assert.closeTo(cc._color.coords[1], 39, 1); assert.closeTo(cc._color.coords[2], 28, 1); assert.equal(cc._color.alpha, 0.8); - cc.setBlue(244/255); + cc.setBlue(244 / 255); assert.closeTo(cc._color.coords[0], 256, 1); assert.closeTo(cc._color.coords[1], 90, 1); assert.closeTo(cc._color.coords[2], 56, 1); @@ -451,44 +457,44 @@ suite('p5.Color', function() { }); }); - suite('p5.Color in HSL mode with RGB string', function() { + suite('p5.Color in HSL mode with RGB string', function () { // NOTE: will still create a sRGB color in this case - beforeEach(function() { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSL, 360, 100, 100, 1); c = mockP5Prototype.color('rgba(255, 0, 102, 0.8)'); }); - test.todo('should correctly get HSLA property', function() { + test.todo('should correctly get HSLA property', function () { assert.approximately(c._getHue(), 336, 0.5); assert.approximately(c._getSaturation(), 100, 0.5); assert.approximately(c._getLightness(), 50, 0.5); assert.approximately(c._getAlpha(), 0.8, 0.05); }); - test('should correctly convert to RGBA', function() { + test('should correctly convert to RGBA', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 0.8); }); - test('should correctly render color string', function() { + test('should correctly render color string', function () { assert.equal(c.toString(), 'rgba(255, 0, 102, 0.8)'); }); }); - suite('p5.Color in HSL mode with HSL string', function() { - beforeEach(function() { + suite('p5.Color in HSL mode with HSL string', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSL, 360, 100, 100, 1); c = mockP5Prototype.color('hsla(336, 100%, 50%, 0.8)'); }); - test.todo('should correctly get HSLA property', function() { + test.todo('should correctly get HSLA property', function () { assert.approximately(c._getHue(), 336, 0.5); assert.approximately(c._getSaturation(), 100, 0.5); assert.approximately(c._getLightness(), 50, 0.5); assert.approximately(c._getAlpha(), 0.8, 0.05); }); - test('should correctly convert to RGBA', function() { + test('should correctly convert to RGBA', function () { // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version // assert.deepEqual(c.color.coords, [336, 100, 50]); assert.equal(+c._color.coords[0], 336); @@ -497,25 +503,25 @@ suite('p5.Color', function() { assert.equal(c._color.alpha, 0.8); }); - test('should correctly render color string', function() { + test('should correctly render color string', function () { assert.equal(c.toString(), 'hsla(336, 100%, 50%, 0.8)'); }); }); - suite('p5.Color in HSL mode with HSB string', function() { - beforeEach(function() { + suite('p5.Color in HSL mode with HSB string', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSL, 360, 100, 100, 1); c = mockP5Prototype.color('hsba(336, 100%, 100%, 0.8)'); }); - test.todo('should correctly get HSLA property', function() { + test.todo('should correctly get HSLA property', function () { assert.approximately(c._getHue(), 336, 0.5); assert.approximately(c._getSaturation(), 100, 0.5); assert.approximately(c._getLightness(), 50, 0.5); assert.approximately(c._getAlpha(), 0.8, 0.05); }); - test('should correctly convert to RGBA', function() { + test('should correctly convert to RGBA', function () { // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version // assert.deepEqual(c.color.coords, [336, 100, 50]); assert.equal(+c._color.coords[0], 336); @@ -524,51 +530,51 @@ suite('p5.Color', function() { assert.equal(c._color.alpha, 0.8); }); - test('should correctly render color string', function() { + test('should correctly render color string', function () { assert.equal(c.toString(), 'hsba(336, 100%, 100%, 0.8)'); }); }); - suite('p5.Color in HSB mode', function() { - beforeEach(function() { + suite('p5.Color in HSB mode', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSB); c = mockP5Prototype.color(336, 100, 100); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [336, 100, 100]); assert.equal(c._color.alpha, 1); }); - test('can be modified with alpha setter', function() { + test('can be modified with alpha setter', function () { var cc = mockP5Prototype.color(336, 100, 100); cc.setAlpha(0.73); assert.deepEqual(cc._color.coords, [336, 100, 100]); assert.equal(cc._color.alpha, 0.73); }); - test('can be modified with rgb setters', function() { + test('can be modified with rgb setters', function () { var cc = mockP5Prototype.color(336, 100, 100); assert.deepEqual(cc._color.coords, [336, 100, 100]); assert.equal(cc._color.alpha, 1); - cc.setRed(98/255); + cc.setRed(98 / 255); assert.closeTo(cc._color.coords[0], 297, 1); assert.closeTo(cc._color.coords[1], 100, 1); assert.closeTo(cc._color.coords[2], 40, 1); assert.equal(cc._color.alpha, 1); - cc.setGreen(44/255); + cc.setGreen(44 / 255); assert.closeTo(cc._color.coords[0], 295, 1); assert.closeTo(cc._color.coords[1], 56, 1); assert.closeTo(cc._color.coords[2], 40, 1); assert.equal(cc._color.alpha, 1); - cc.setBlue(244/255); + cc.setBlue(244 / 255); assert.closeTo(cc._color.coords[0], 256, 1); assert.closeTo(cc._color.coords[1], 81, 1); assert.closeTo(cc._color.coords[2], 95, 1); @@ -576,20 +582,20 @@ suite('p5.Color', function() { }); }); - suite('p5.Color in HSB mode with Alpha', function() { - beforeEach(function() { + suite('p5.Color in HSB mode with Alpha', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSB); c = mockP5Prototype.color(336, 100, 100, 0.8); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGBA property', function() { + test('should correctly set RGBA property', function () { assert.deepEqual(c._color.coords, [336, 100, 100]); assert.equal(c._color.alpha, 0.8); }); - test('should correctly get hue/saturation/brightness/alpha', function() { + test('should correctly get hue/saturation/brightness/alpha', function () { assert.approximately(c._getHue(), 336, 0.5); assert.approximately(c._getSaturation(), 100, 0.5); assert.approximately(c._getBrightness(), 100, 0.5); @@ -597,210 +603,210 @@ suite('p5.Color', function() { }); }); - suite('p5.Color in HSB mode with custom range', function() { - beforeEach(function() { + suite('p5.Color in HSB mode with custom range', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSB, 100, 200, 300, 10); c = mockP5Prototype.color(93.33, 200, 300, 8); }); - test('should correctly get HSBA property', function() { + test('should correctly get HSBA property', function () { assert.approximately(c._getHue(), 336, 0.5); assert.approximately(c._getSaturation(), 100, 0.5); assert.approximately(c._getBrightness(), 100, 0.5); assert.approximately(c._getAlpha(), 0.8, 0.5); }); - test('should correctly convert to RGBA', function() { + test('should correctly convert to RGBA', function () { assert.closeTo(c._color.coords[0], 336, 1); assert.equal(c._color.coords[1], 100); assert.equal(c._color.coords[2], 100); assert.equal(c._color.alpha, 0.8); }); - test('should correctly render color string', function() { + test('should correctly render color string', function () { assert.equal(c.toString(), 'rgb(100% 0% 40.02% / 0.8)'); }); }); - suite('p5.Color in HSB mode with RGB string', function() { - beforeEach(function() { + suite('p5.Color in HSB mode with RGB string', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSB, 360, 100, 100, 1); c = mockP5Prototype.color('rgba(255, 0, 102, 0.8)'); }); - test.todo('should correctly get HSBA property', function() { + test.todo('should correctly get HSBA property', function () { assert.approximately(c._getHue(), 336, 0.5); assert.approximately(c._getSaturation(), 100, 0.5); assert.approximately(c._getBrightness(), 100, 0.5); assert.approximately(c._getAlpha(), 0.8, 0.05); }); - test('should correctly convert to RGBA', function() { + test('should correctly convert to RGBA', function () { assert.deepEqual(c._color.coords, [1, 0, 0.4]); assert.equal(c._color.alpha, 0.8); }); - test('should correctly render color string', function() { + test('should correctly render color string', function () { assert.equal(c.toString(), 'rgba(255, 0, 102, 0.8)'); }); }); - suite('p5.Color in HSB mode with HSB string', function() { - beforeEach(function() { + suite('p5.Color in HSB mode with HSB string', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSB, 360, 100, 100, 1); c = mockP5Prototype.color('hsba(336, 100%, 100%, 0.8)'); }); - test('should correctly get HSBA property', function() { + test('should correctly get HSBA property', function () { assert.approximately(c._getHue(), 336, 0.5); assert.approximately(c._getSaturation(), 100, 0.5); assert.approximately(c._getBrightness(), 100, 0.5); assert.approximately(c._getAlpha(), 0.8, 0.05); }); - test('should correctly convert to RGBA', function() { + test('should correctly convert to RGBA', function () { assert.equal(+c._color.coords[0], 336); assert.equal(c._color.coords[1], 100); assert.equal(c._color.coords[2], 100); assert.equal(c._color.alpha, 0.8); }); - test('should correctly render color string', function() { + test('should correctly render color string', function () { assert.equal(c.toString(), 'hsba(336, 100%, 100%, 0.8)'); }); }); - suite('p5.Color in HSB mode with HSL string', function() { - beforeEach(function() { + suite('p5.Color in HSB mode with HSL string', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSB, 360, 100, 100, 1); c = mockP5Prototype.color('hsla(336, 100%, 50%, 0.8)'); }); - test.todo('should correctly get HSBA property', function() { + test.todo('should correctly get HSBA property', function () { assert.approximately(c._getHue(), 336, 0.5); assert.approximately(c._getSaturation(), 100, 0.5); assert.approximately(c._getBrightness(), 100, 0.5); assert.approximately(c._getAlpha(), 0.8, 0.05); }); - test('should correctly convert to RGBA', function() { + test('should correctly convert to RGBA', function () { assert.equal(+c._color.coords[0], 336); assert.equal(c._color.coords[1], 100); assert.equal(c._color.coords[2], 50); assert.equal(c._color.alpha, 0.8); }); - test('should correctly render color string', function() { + test('should correctly render color string', function () { assert.equal(c.toString(), 'hsla(336, 100%, 50%, 0.8)'); }); }); - suite('p5.Color in RGB mode with grayscale value', function() { - beforeEach(function() { + suite('p5.Color in RGB mode with grayscale value', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.RGB); c = mockP5Prototype.color(100); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGB levels', function() { - assert.deepEqual(c._color.coords, [100/255, 100/255, 100/255]); + test('should correctly set RGB levels', function () { + assert.deepEqual(c._color.coords, [100 / 255, 100 / 255, 100 / 255]); assert.equal(c._color.alpha, 1); }); }); - suite('p5.Color in RGB mode with grayscale value and alpha', function() { - beforeEach(function() { + suite('p5.Color in RGB mode with grayscale value and alpha', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.RGB); c = mockP5Prototype.color(100, 70); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGB levels', function() { - assert.deepEqual(c._color.coords, [100/255, 100/255, 100/255]); - assert.equal(c._color.alpha, 70/255); + test('should correctly set RGB levels', function () { + assert.deepEqual(c._color.coords, [100 / 255, 100 / 255, 100 / 255]); + assert.equal(c._color.alpha, 70 / 255); }); }); - suite('p5.Color in HSB mode with grayscale value', function() { - beforeEach(function() { + suite('p5.Color in HSB mode with grayscale value', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSB); c = mockP5Prototype.color(39.3); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set color coordinates', function() { + test('should correctly set color coordinates', function () { assert.deepEqual(c._color.coords, [0, 0, 39.3]); assert.equal(c._color.alpha, 1); }); }); - suite('p5.Color in HSB mode with grayscale value and alpha', function() { - beforeEach(function() { + suite('p5.Color in HSB mode with grayscale value and alpha', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSB); c = mockP5Prototype.color(39.3, 0.275); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGB levels', function() { + test('should correctly set RGB levels', function () { assert.deepEqual(c._color.coords, [0, 0, 39.3]); assert.equal(c._color.alpha, 0.275); }); }); - suite('p5.Color in HSL mode with grayscale value', function() { - beforeEach(function() { + suite('p5.Color in HSL mode with grayscale value', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSL); c = mockP5Prototype.color(39.3); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGB levels', function() { + test('should correctly set RGB levels', function () { assert.deepEqual(c._color.coords, [0, 0, 39.3]); assert.equal(c._color.alpha, 1); }); }); - suite('p5.Color in HSL mode with grayscale value and alpha', function() { - beforeEach(function() { + suite('p5.Color in HSL mode with grayscale value and alpha', function () { + beforeEach(function () { mockP5Prototype.colorMode(mockP5Prototype.HSL); c = mockP5Prototype.color(39.3, 0.275); }); - test('should create instance of p5.Color', function() { + test('should create instance of p5.Color', function () { assert.instanceOf(c, Color); }); - test('should correctly set RGB levels', function() { + test('should correctly set RGB levels', function () { assert.deepEqual(c._color.coords, [0, 0, 39.3]); assert.equal(c._color.alpha, 0.275); }); }); - suite('p5.Color.prototype.toString', function() { - suite('#rrggbb format', function() { - test('should expand short hex (#rgb) to full 6-digit format', function() { + suite('p5.Color.prototype.toString', function () { + suite('#rrggbb format', function () { + test('should expand short hex (#rgb) to full 6-digit format', function () { mockP5Prototype.colorMode(mockP5Prototype.RGB, 255, 255, 255, 255); const c = mockP5Prototype.color('#f06'); const result = c.toString('#rrggbb'); assert.equal(result, '#ff0066'); }); - test('should truncate hex with alpha (#rrggbbaa) to 6 digits', function() { + test('should truncate hex with alpha (#rrggbbaa) to 6 digits', function () { mockP5Prototype.colorMode(mockP5Prototype.RGB, 255, 255, 255, 255); const c = mockP5Prototype.color(255, 0, 102, 128); const result = c.toString('#rrggbb'); @@ -808,7 +814,7 @@ suite('p5.Color', function() { assert.equal(result, '#ff0066'); }); - test('should output lowercase hex string', function() { + test('should output lowercase hex string', function () { mockP5Prototype.colorMode(mockP5Prototype.RGB, 255, 255, 255, 255); const c = mockP5Prototype.color(255, 170, 187); const result = c.toString('#rrggbb'); @@ -816,28 +822,28 @@ suite('p5.Color', function() { assert.equal(result, '#ffaabb'); }); - test('should correctly format standard RGB colors', function() { + test('should correctly format standard RGB colors', function () { mockP5Prototype.colorMode(mockP5Prototype.RGB, 255, 255, 255, 255); const c = mockP5Prototype.color(255, 0, 102); const result = c.toString('#rrggbb'); assert.equal(result, '#ff0066'); }); - test('should correctly format black color', function() { + test('should correctly format black color', function () { mockP5Prototype.colorMode(mockP5Prototype.RGB, 255, 255, 255, 255); const c = mockP5Prototype.color(0, 0, 0); const result = c.toString('#rrggbb'); assert.equal(result, '#000000'); }); - test('should correctly format white color', function() { + test('should correctly format white color', function () { mockP5Prototype.colorMode(mockP5Prototype.RGB, 255, 255, 255, 255); const c = mockP5Prototype.color(255, 255, 255); const result = c.toString('#rrggbb'); assert.equal(result, '#ffffff'); }); - test('should handle colors created from 6-digit hex string', function() { + test('should handle colors created from 6-digit hex string', function () { mockP5Prototype.colorMode(mockP5Prototype.RGB, 255, 255, 255, 255); const c = mockP5Prototype.color('#9932cc'); const result = c.toString('#rrggbb'); diff --git a/test/unit/color/setting.js b/test/unit/color/setting.js index ba70afa7a0..eaa56f8803 100644 --- a/test/unit/color/setting.js +++ b/test/unit/color/setting.js @@ -7,7 +7,7 @@ import setting from '../../../src/color/setting'; import { States } from '../../../src/core/States'; // NOTE: Require ESM compatible libtess -suite('color/Setting', function() { +suite('color/Setting', function () { let myp5; // sketch without WEBGL Mode let my3D; // sketch with WEBGL mode @@ -28,10 +28,10 @@ suite('color/Setting', function() { delete mockP5Prototype._colorMaxes; }); - beforeEach(async function() { + beforeEach(async function () { await new Promise(resolve => { - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100, p.WEBGL); my3D = p; resolve(); @@ -40,8 +40,8 @@ suite('color/Setting', function() { }); await new Promise(resolve => { - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { myp5 = p; resolve(); }; @@ -49,12 +49,12 @@ suite('color/Setting', function() { }); }); - afterEach(function() { + afterEach(function () { myp5.remove(); my3D.remove(); }); - suite('p5.prototype.erase', function() { + suite('p5.prototype.erase', function () { beforeEach(() => { mockP5Prototype._renderer = { erase: vi.fn(), @@ -64,7 +64,7 @@ suite('color/Setting', function() { rgb: [255, 255, 255, 255], hsb: [360, 100, 100, 1], hsl: [360, 100, 100, 1], - clone: function() { + clone: function () { // Quick incorrect implementation for this test return this; } @@ -77,38 +77,38 @@ suite('color/Setting', function() { vi.resetAllMocks(); }); - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.erase); }); - test('should call the renderer erase method with default arguments', function() { + test('should call the renderer erase method with default arguments', function () { mockP5Prototype.erase(); expect(mockP5Prototype._renderer.erase).toHaveBeenCalledWith(255, 255); }); // TODO: test in renderer test.todo('should set renderer to erasing state'); - test.todo('should cache renderer fill', function() { + test.todo('should cache renderer fill', function () { myp5.fill(255, 0, 0); const fillStyle = myp5.drawingContext.fillStyle; myp5.erase(); assert.deepEqual(myp5._renderer._cachedFillStyle, fillStyle); }); - test.todo('should cache renderer stroke', function() { + test.todo('should cache renderer stroke', function () { myp5.stroke(255, 0, 0); const strokeStyle = myp5.drawingContext.strokeStyle; myp5.erase(); assert.deepEqual(myp5._renderer._cachedStrokeStyle, strokeStyle); }); - test.todo('should cache renderer blend', function() { + test.todo('should cache renderer blend', function () { myp5.blendMode(myp5.SCREEN); myp5.erase(); assert.deepEqual(myp5._renderer._cachedBlendMode, myp5.SCREEN); }); - test.todo('should set fill strength', function() { + test.todo('should set fill strength', function () { myp5.erase(125); assert.equal( myp5.color(myp5.drawingContext.fillStyle).array, @@ -116,7 +116,7 @@ suite('color/Setting', function() { ); }); - test.todo('should set stroke strength', function() { + test.todo('should set stroke strength', function () { myp5.erase(255, 50); assert.equal( myp5.color(myp5.drawingContext.strokeStyle).array, @@ -125,67 +125,71 @@ suite('color/Setting', function() { }); }); - suite('p5.RendererGL.prototype.erase', function() { - test('should set renderer to erasing state', function() { + suite('p5.RendererGL.prototype.erase', function () { + test('should set renderer to erasing state', function () { my3D.erase(); assert.isTrue(my3D._renderer._isErasing); }); - test.todo('should cache renderer fill', function() { + test.todo('should cache renderer fill', function () { my3D.fill(255, 0, 0); const curFillColor = my3D._renderer.states.curFillColor; my3D.erase(); assert.deepEqual(my3D._renderer._cachedFillStyle, curFillColor); }); - test.todo('should cache renderer stroke', function() { + test.todo('should cache renderer stroke', function () { my3D.stroke(255, 0, 0); const strokeStyle = my3D._renderer.states.curStrokeColor; my3D.erase(); assert.deepEqual(my3D._renderer._cachedStrokeStyle, strokeStyle); }); - test('should cache renderer blend', function() { + test('should cache renderer blend', function () { my3D.blendMode(my3D.SCREEN); my3D.erase(); assert.deepEqual(my3D._renderer.preEraseBlend, my3D.SCREEN); }); - test('should set fill strength', function() { + test('should set fill strength', function () { my3D.erase(125); - assert.deepEqual( - my3D._renderer.states.curFillColor, - [1, 1, 1, 125 / 255] - ); + assert.deepEqual(my3D._renderer.states.curFillColor, [ + 1, + 1, + 1, + 125 / 255 + ]); }); - test('should set stroke strength', function() { + test('should set stroke strength', function () { my3D.erase(255, 50); - assert.deepEqual( - my3D._renderer.states.curStrokeColor, - [1, 1, 1, 50 / 255] - ); + assert.deepEqual(my3D._renderer.states.curStrokeColor, [ + 1, + 1, + 1, + 50 / 255 + ]); }); - test('should set default values when no arguments', function() { + test('should set default values when no arguments', function () { my3D.erase(); assert.deepEqual(my3D._renderer.states.curFillColor, [1, 1, 1, 1]); assert.deepEqual(my3D._renderer.states.curStrokeColor, [1, 1, 1, 1]); }); }); - suite('p5.prototype.noErase', function() { - test('should be a function', function() { + suite('p5.prototype.noErase', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.noErase); }); - test('should turn off renderer erasing state', function() { + test('should turn off renderer erasing state', function () { myp5.erase(); myp5.noErase(); assert.isFalse(myp5._renderer._isErasing); }); - test.todo('should restore cached renderer fill', function() { + test.todo('should restore cached renderer fill', function () { myp5.fill(255, 0, 0); const fillStyle = myp5.drawingContext.fillStyle; myp5.erase(); @@ -193,7 +197,7 @@ suite('color/Setting', function() { assert.deepEqual(myp5.drawingContext.fillStyle, fillStyle); }); - test.todo('should restore cached renderer stroke', function() { + test.todo('should restore cached renderer stroke', function () { myp5.stroke(255, 0, 0); const strokeStyle = myp5.drawingContext.strokeStyle; myp5.erase(); @@ -202,14 +206,14 @@ suite('color/Setting', function() { }); }); - suite('p5.RendererGL.prototype.noErase', function() { - test('should turn off renderer erasing state', function() { + suite('p5.RendererGL.prototype.noErase', function () { + test('should turn off renderer erasing state', function () { my3D.erase(); my3D.noErase(); assert.isFalse(my3D._renderer._isErasing); }); - test.todo('should restore cached renderer fill', function() { + test.todo('should restore cached renderer fill', function () { my3D.fill(255, 0, 0); const fillStyle = my3D._renderer.states.curFillColor.slice(); my3D.erase(); @@ -217,7 +221,7 @@ suite('color/Setting', function() { assert.deepEqual([1, 0, 0, 1], fillStyle); }); - test.todo('should restore cached renderer stroke', function() { + test.todo('should restore cached renderer stroke', function () { my3D.stroke(255, 0, 0); const strokeStyle = my3D._renderer.states.curStrokeColor.slice(); my3D.erase(); @@ -226,60 +230,96 @@ suite('color/Setting', function() { }); }); - suite('p5.prototype.colorMode', function() { - test('should be a function', function() { + suite('p5.prototype.colorMode', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.colorMode); }); - test('should set mode to RGB', function() { + test('should set mode to RGB', function () { mockP5Prototype.colorMode('rgb'); assert.equal(mockP5Prototype._renderer.states.colorMode, 'rgb'); }); - test('should correctly set color RGB maxes', function() { - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['rgb'], [255, 255, 255, 255]); + test('should correctly set color RGB maxes', function () { + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['rgb'], + [255, 255, 255, 255] + ); mockP5Prototype.colorMode('rgb', 1, 1, 1); - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['rgb'], [1, 1, 1, 255]); + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['rgb'], + [1, 1, 1, 255] + ); mockP5Prototype.colorMode('rgb', 1); - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['rgb'], [1, 1, 1, 1]); + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['rgb'], + [1, 1, 1, 1] + ); mockP5Prototype.colorMode('rgb', 255, 255, 255, 1); - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['rgb'], [255, 255, 255, 1]); + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['rgb'], + [255, 255, 255, 1] + ); mockP5Prototype.colorMode('rgb', 255); }); - test('should set mode to HSL', function() { + test('should set mode to HSL', function () { mockP5Prototype.colorMode('hsl'); assert.equal(mockP5Prototype._renderer.states.colorMode, 'hsl'); }); - test('should correctly set color HSL maxes', function() { - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsl'], [360, 100, 100, 1]); + test('should correctly set color HSL maxes', function () { + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['hsl'], + [360, 100, 100, 1] + ); mockP5Prototype.colorMode('hsl', 255, 255, 255); - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsl'], [255, 255, 255, 1]); + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['hsl'], + [255, 255, 255, 1] + ); mockP5Prototype.colorMode('hsl', 360); - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsl'], [360, 360, 360, 360]); + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['hsl'], + [360, 360, 360, 360] + ); mockP5Prototype.colorMode('hsl', 360, 100, 100, 1); - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsl'], [360, 100, 100, 1]); + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['hsl'], + [360, 100, 100, 1] + ); }); - test('should set mode to HSB', function() { + test('should set mode to HSB', function () { mockP5Prototype.colorMode('hsb'); assert.equal(mockP5Prototype._renderer.states.colorMode, 'hsb'); }); - test('should correctly set color HSB maxes', function() { - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsb'], [360, 100, 100, 1]); + test('should correctly set color HSB maxes', function () { + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['hsb'], + [360, 100, 100, 1] + ); mockP5Prototype.colorMode('hsb', 255, 255, 255); - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsb'], [255, 255, 255, 1]); + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['hsb'], + [255, 255, 255, 1] + ); mockP5Prototype.colorMode('hsb', 360); - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsb'], [360, 360, 360, 360]); + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['hsb'], + [360, 360, 360, 360] + ); mockP5Prototype.colorMode('hsb', 360, 100, 100, 1); - assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsb'], [360, 100, 100, 1]); + assert.deepEqual( + mockP5Prototype._renderer.states.colorMaxes['hsb'], + [360, 100, 100, 1] + ); }); }); - suite('p5.Color components', function() { - test('setRed() correctly sets red component', function() { + suite('p5.Color components', function () { + test('setRed() correctly sets red component', function () { mockP5Prototype.colorMode('rgb', 255); const c = mockP5Prototype.color(0, 162, 205, 255); c.setRed(100); @@ -289,7 +329,7 @@ suite('color/Setting', function() { assert.equal(mockP5Prototype.alpha(c), 255); }); - test('setGreen() correctly sets green component', function() { + test('setGreen() correctly sets green component', function () { mockP5Prototype.colorMode('rgb', 255); const c = mockP5Prototype.color(0, 162, 205, 255); c.setGreen(100); @@ -299,7 +339,7 @@ suite('color/Setting', function() { assert.equal(mockP5Prototype.alpha(c), 255); }); - test('setBlue() correctly sets blue component', function() { + test('setBlue() correctly sets blue component', function () { mockP5Prototype.colorMode('rgb', 255); const c = mockP5Prototype.color(0, 162, 205, 255); c.setBlue(100); @@ -309,7 +349,7 @@ suite('color/Setting', function() { assert.equal(mockP5Prototype.alpha(c), 255); }); - test('setAlpha correctly sets alpha component', function() { + test('setAlpha correctly sets alpha component', function () { mockP5Prototype.colorMode('rgb', 255); const c = mockP5Prototype.color(0, 162, 205, 255); c.setAlpha(100); @@ -319,32 +359,32 @@ suite('color/Setting', function() { assert.equal(mockP5Prototype.alpha(c), 100); }); - test('changing the red/green/blue/alpha components should clear the cached HSL/HSB values', function() { + test('changing the red/green/blue/alpha components should clear the cached HSL/HSB values', function () { mockP5Prototype.colorMode('rgb', 255); const c = mockP5Prototype.color(0, 162, 205, 255); // create HSL/HSB values mockP5Prototype.lightness(c); mockP5Prototype.brightness(c); - c.setRed(100/255); + c.setRed(100 / 255); assert(!c.hsba); assert(!c.hsla); mockP5Prototype.lightness(c); mockP5Prototype.brightness(c); - c.setGreen(100/255); + c.setGreen(100 / 255); assert(!c.hsba); assert(!c.hsla); mockP5Prototype.lightness(c); mockP5Prototype.brightness(c); - c.setBlue(100/255); + c.setBlue(100 / 255); assert(!c.hsba); assert(!c.hsla); mockP5Prototype.lightness(c); mockP5Prototype.brightness(c); - c.setAlpha(100/255); + c.setAlpha(100 / 255); assert(!c.hsba); assert(!c.hsla); }); diff --git a/test/unit/core/2d_primitives.js b/test/unit/core/2d_primitives.js index a85e07ae87..528463fde8 100644 --- a/test/unit/core/2d_primitives.js +++ b/test/unit/core/2d_primitives.js @@ -1,68 +1,68 @@ import { mockP5, mockP5Prototype } from '../../js/mocks'; import primitives from '../../../src/shape/2d_primitives'; -suite('2D Primitives', function() { - beforeAll(function() { +suite('2D Primitives', function () { + beforeAll(function () { primitives(mockP5, mockP5Prototype); }); - suite('p5.prototype.arc', function() { - test('should be a function', function() { + suite('p5.prototype.arc', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.arc); assert.typeOf(mockP5Prototype.arc, 'function'); }); }); - suite('p5.prototype.ellipse', function() { - test('should be a function', function() { + suite('p5.prototype.ellipse', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.ellipse); assert.typeOf(mockP5Prototype.ellipse, 'function'); }); }); - suite('p5.prototype.line', function() { - test('should be a function', function() { + suite('p5.prototype.line', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.line); assert.typeOf(mockP5Prototype.line, 'function'); }); }); - suite('p5.prototype.point', function() { - test('should be a function', function() { + suite('p5.prototype.point', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.point); assert.typeOf(mockP5Prototype.point, 'function'); }); }); - suite('p5.prototype.quad', function() { - test('should be a function', function() { + suite('p5.prototype.quad', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.quad); assert.typeOf(mockP5Prototype.quad, 'function'); }); }); - suite('p5.prototype.rect', function() { - test('should be a function', function() { + suite('p5.prototype.rect', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.rect); assert.typeOf(mockP5Prototype.rect, 'function'); }); }); - suite('p5.prototype.triangle', function() { - test('should be a function', function() { + suite('p5.prototype.triangle', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.triangle); assert.typeOf(mockP5Prototype.triangle, 'function'); }); }); - suite('p5.prototype.square', function() { - test('should be a function', function() { + suite('p5.prototype.square', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.square); assert.typeOf(mockP5Prototype.square, 'function'); }); }); - suite('p5.prototype._normalizeArcAngles', function() { - test('start/stop both at zero', function() { + suite('p5.prototype._normalizeArcAngles', function () { + test('start/stop both at zero', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -79,7 +79,7 @@ suite('2D Primitives', function() { } } }); - test('start/stop same but non-zero', function() { + test('start/stop same but non-zero', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -96,7 +96,7 @@ suite('2D Primitives', function() { } } }); - test('start/stop both close to zero, start < stop', function() { + test('start/stop both close to zero, start < stop', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -113,7 +113,7 @@ suite('2D Primitives', function() { } } }); - test('start/stop both close to zero, start > stop', function() { + test('start/stop both close to zero, start > stop', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -130,7 +130,7 @@ suite('2D Primitives', function() { } } }); - test('start/stop both close to same non-zero, start < stop', function() { + test('start/stop both close to same non-zero, start < stop', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -147,7 +147,7 @@ suite('2D Primitives', function() { } } }); - test('start/stop both close to same non-zero, start > stop', function() { + test('start/stop both close to same non-zero, start > stop', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -164,7 +164,7 @@ suite('2D Primitives', function() { } } }); - test('start/stop around zero but not close, start < stop', function() { + test('start/stop around zero but not close, start < stop', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -181,7 +181,7 @@ suite('2D Primitives', function() { } } }); - test('start/stop around zero but not close, start > stop', function() { + test('start/stop around zero but not close, start > stop', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -198,7 +198,7 @@ suite('2D Primitives', function() { } } }); - test('start/stop away from zero and not close, start < stop', function() { + test('start/stop away from zero and not close, start < stop', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -215,7 +215,7 @@ suite('2D Primitives', function() { } } }); - test('start/stop away from zero and not close, start > stop', function() { + test('start/stop away from zero and not close, start > stop', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -232,7 +232,7 @@ suite('2D Primitives', function() { } } }); - test('scaling correction, quadrants 1 and 3', function() { + test('scaling correction, quadrants 1 and 3', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { @@ -249,7 +249,7 @@ suite('2D Primitives', function() { } } }); - test('scaling correction, quadrants 2 and 4', function() { + test('scaling correction, quadrants 2 and 4', function () { var i, j, angles; for (i = -2; i <= 2; i++) { for (j = -2; j <= 2; j++) { diff --git a/test/unit/core/States.js b/test/unit/core/States.js index d4b2b56a35..56288a490a 100644 --- a/test/unit/core/States.js +++ b/test/unit/core/States.js @@ -67,4 +67,4 @@ suite('States', function () { assert.equal(s.fill, 'red'); assert.deepEqual(s.getModified(), {}); }); -}); \ No newline at end of file +}); diff --git a/test/unit/core/attributes.js b/test/unit/core/attributes.js index 6cd0f416e2..c5c7997c38 100644 --- a/test/unit/core/attributes.js +++ b/test/unit/core/attributes.js @@ -1,55 +1,55 @@ import { mockP5, mockP5Prototype } from '../../js/mocks'; import attributes from '../../../src/shape/attributes'; -suite('Attributes', function() { - beforeAll(function() { +suite('Attributes', function () { + beforeAll(function () { attributes(mockP5, mockP5Prototype); }); - suite('p5.prototype.ellipseMode', function() { - test('should be a function', function() { + suite('p5.prototype.ellipseMode', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.ellipseMode); assert.typeOf(mockP5Prototype.ellipseMode, 'function'); }); }); - suite('p5.prototype.rectMode', function() { - test('should be a function', function() { + suite('p5.prototype.rectMode', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.rectMode); assert.typeOf(mockP5Prototype.rectMode, 'function'); }); }); - suite('p5.prototype.noSmooth', function() { - test('should be a function', function() { + suite('p5.prototype.noSmooth', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.noSmooth); assert.typeOf(mockP5Prototype.noSmooth, 'function'); }); }); - suite('p5.prototype.smooth', function() { - test('should be a function', function() { + suite('p5.prototype.smooth', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.smooth); assert.typeOf(mockP5Prototype.smooth, 'function'); }); }); - suite('p5.prototype.strokeCap', function() { - test('should be a function', function() { + suite('p5.prototype.strokeCap', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.strokeCap); assert.typeOf(mockP5Prototype.strokeCap, 'function'); }); }); - suite('p5.prototype.strokeJoin', function() { - test('should be a function', function() { + suite('p5.prototype.strokeJoin', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.strokeJoin); assert.typeOf(mockP5Prototype.strokeJoin, 'function'); }); }); - suite('p5.prototype.strokeWeight', function() { - test('should be a function', function() { + suite('p5.prototype.strokeWeight', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.strokeWeight); assert.typeOf(mockP5Prototype.strokeWeight, 'function'); }); diff --git a/test/unit/core/curves.js b/test/unit/core/curves.js index 0e79fd6863..2be8db6ecd 100644 --- a/test/unit/core/curves.js +++ b/test/unit/core/curves.js @@ -2,8 +2,8 @@ import { mockP5, mockP5Prototype } from '../../js/mocks'; import curves from '../../../src/shape/curves'; import { States } from '../../../src/core/States'; -suite('Curves', function() { - beforeAll(function() { +suite('Curves', function () { + beforeAll(function () { mockP5Prototype._renderer = { states: new States({ splineProperties: { @@ -18,65 +18,65 @@ suite('Curves', function() { delete mockP5Prototype._renderer; }); - suite('p5.prototype.bezier', function() { - test('should be a function', function() { + suite('p5.prototype.bezier', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.bezier); assert.typeOf(mockP5Prototype.bezier, 'function'); }); }); - suite('p5.prototype.bezierPoint', function() { + suite('p5.prototype.bezierPoint', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.bezierPoint); assert.typeOf(mockP5Prototype.bezierPoint, 'function'); }); - test('should return the correct point on a Bezier Curve', function() { + test('should return the correct point on a Bezier Curve', function () { result = mockP5Prototype.bezierPoint(85, 10, 90, 15, 0.5); assert.equal(result, 50); assert.notEqual(result, -1); }); }); - suite('p5.prototype.bezierTangent', function() { + suite('p5.prototype.bezierTangent', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.bezierTangent); assert.typeOf(mockP5Prototype.bezierTangent, 'function'); }); - test('should return the correct point on a Bezier Curve', function() { + test('should return the correct point on a Bezier Curve', function () { result = mockP5Prototype.bezierTangent(95, 73, 73, 15, 0.5); assert.equal(result, -60); }); }); - suite('p5.prototype.spline', function() { - test('should be a function', function() { + suite('p5.prototype.spline', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.spline); assert.typeOf(mockP5Prototype.spline, 'function'); }); }); - suite('p5.prototype.splinePoint', function() { + suite('p5.prototype.splinePoint', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.splinePoint); assert.typeOf(mockP5Prototype.splinePoint, 'function'); }); - test('should return the correct point on a Catmull-Rom Curve', function() { + test('should return the correct point on a Catmull-Rom Curve', function () { result = mockP5Prototype.splinePoint(5, 5, 73, 73, 0.5); assert.equal(result, 39); assert.notEqual(result, -1); }); }); - suite('p5.prototype.splineTangent', function() { + suite('p5.prototype.splineTangent', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.splineTangent); assert.typeOf(mockP5Prototype.splineTangent, 'function'); }); - test('should return the correct point on a Catmull-Rom Curve', function() { + test('should return the correct point on a Catmull-Rom Curve', function () { result = mockP5Prototype.splineTangent(95, 73, 73, 15, 0.5); assert.equal(result, 10); assert.notEqual(result, -1); diff --git a/test/unit/core/environment.js b/test/unit/core/environment.js index 590865c7dd..9438e5c9e3 100644 --- a/test/unit/core/environment.js +++ b/test/unit/core/environment.js @@ -1,32 +1,32 @@ import p5 from '../../../src/app.js'; import { vi } from 'vitest'; -suite('Environment', function() { +suite('Environment', function () { var myp5; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('p5.frameCount', function() { - test('starts at zero', function() { - return new Promise(function(resolve, reject) { + suite('p5.frameCount', function () { + test('starts at zero', function () { + return new Promise(function (resolve, reject) { // Has to use a custom p5 to hook setup correctly - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { if (p.frameCount !== 0) { reject('frameCount is not 0 in setup'); } }; - p.draw = function() { + p.draw = function () { if (p.frameCount === 1) { resolve(); } @@ -34,11 +34,11 @@ suite('Environment', function() { }); }); }); - test('matches draw calls', function() { - return new Promise(function(resolve, reject) { + test('matches draw calls', function () { + return new Promise(function (resolve, reject) { var frames = myp5.frameCount; var start = myp5.frameCount; - myp5.draw = function() { + myp5.draw = function () { try { frames += 1; assert.equal(myp5.frameCount, frames); @@ -69,41 +69,41 @@ suite('Environment', function() { }); }); - suite('p5.prototype.focused', function() { - test('it should return true on focus', function() { + suite('p5.prototype.focused', function () { + test('it should return true on focus', function () { window.dispatchEvent(new Event('focus')); assert.strictEqual(myp5.focused, true); }); - test('it should return true on blur', function() { + test('it should return true on blur', function () { window.dispatchEvent(new Event('blur')); assert.strictEqual(myp5.focused, false); }); }); - suite('p5.prototype.cursor', function() { - test('should change cursor to cross', function() { + suite('p5.prototype.cursor', function () { + test('should change cursor to cross', function () { myp5.cursor(myp5.CROSS); assert.strictEqual(myp5._curElement.elt.style.cursor, 'crosshair'); }); }); - suite('p5.prototype.noCursor', function() { - test('should change cursor to none', function() { + suite('p5.prototype.noCursor', function () { + test('should change cursor to none', function () { myp5.noCursor(); assert.strictEqual(myp5._curElement.elt.style.cursor, 'none'); }); }); - suite('p5.prototype.frameRate', function() { - test.todo('returns 0 on first draw call', function() { + suite('p5.prototype.frameRate', function () { + test.todo('returns 0 on first draw call', function () { assert.strictEqual(myp5.frameRate(), 0); }); - test('returns current frame rate after first draw call', function() { - return new Promise(function(resolve, reject) { - new p5(function(p) { - p.draw = function() { + test('returns current frame rate after first draw call', function () { + return new Promise(function (resolve, reject) { + new p5(function (p) { + p.draw = function () { if (p.frameCount === 2 && p.frameRate() > 0) { resolve(); p.remove(); @@ -113,17 +113,17 @@ suite('Environment', function() { }); }); - test.todo('p5.prototype.getFrameRate', function() { + test.todo('p5.prototype.getFrameRate', function () { assert.strictEqual(myp5.getFrameRate(), 0); }); - suite.todo('drawing with target frame rates', function() { + suite.todo('drawing with target frame rates', function () { let clock; let prevRequestAnimationFrame; let nextFrameCallback = () => {}; let controlledP5; - beforeEach(function() { + beforeEach(function () { clock = vi.useFakeTimers(0); vi.spyOn(window.performance, 'now', Date.now); @@ -131,45 +131,51 @@ suite('Environment', function() { prevRequestAnimationFrame = window.requestAnimationFrame; // Use a fake requestAnimationFrame that just stores a ref to the callback // so that we can call it manually - window.requestAnimationFrame = function(cb) { + window.requestAnimationFrame = function (cb) { nextFrameCallback = cb; }; - return new Promise(function(resolve) { - controlledP5 = new p5(function(p) { - p.setup = function() { + return new Promise(function (resolve) { + controlledP5 = new p5(function (p) { + p.setup = function () { p.createCanvas(10, 10); p.frameRate(60); p.loop(); resolve(p); }; - p.draw = function() {}; + p.draw = function () {}; }); }); }); - afterEach(function() { + afterEach(function () { // clock.restore(); vi.restoreAllMocks(); window.performance.now.restore(); window.requestAnimationFrame = prevRequestAnimationFrame; - nextFrameCallback = function() {}; + nextFrameCallback = function () {}; controlledP5.remove(); }); - test('draw() is called at the correct frame rate given a faster display', function() { + test('draw() is called at the correct frame rate given a faster display', function () { vi.spyOn(controlledP5, 'draw'); clock.tick(1000 / 200); // Simulate a 200Hz refresh rate nextFrameCallback(); // trigger the next requestAnimationFrame - assert(controlledP5.draw.notCalled, 'draw() should not be called before 1s/60'); + assert( + controlledP5.draw.notCalled, + 'draw() should not be called before 1s/60' + ); // Advance until 5ms before the next frame should render. // This should be within p5's threshold for rendering the frame. clock.tick(1000 / 60 - 1000 / 200 - 5); nextFrameCallback(); // trigger the next requestAnimationFrame - assert(controlledP5.draw.calledOnce, 'one frame should have been drawn'); + assert( + controlledP5.draw.calledOnce, + 'one frame should have been drawn' + ); // deltaTime should reflect real elapsed time assert.equal(controlledP5.deltaTime, 1000 / 60 - 5); @@ -178,7 +184,10 @@ suite('Environment', function() { nextFrameCallback(); // trigger the next requestAnimationFrame // Even though this is 1s/60 - 5ms from the last draw, the last frame came // in early, so we still shouldn't draw - assert(controlledP5.draw.calledOnce, 'draw() should not be called before 1s/60 past the last target draw time'); + assert( + controlledP5.draw.calledOnce, + 'draw() should not be called before 1s/60 past the last target draw time' + ); // Advance enough time forward to be 1s/60 from the last draw clock.tick(5); @@ -190,66 +199,66 @@ suite('Environment', function() { }); }); - suite('p5.prototype.getTargetFrameRate', function() { - test('returns 60 on the first call', function() { + suite('p5.prototype.getTargetFrameRate', function () { + test('returns 60 on the first call', function () { assert.strictEqual(myp5.getTargetFrameRate(), 60); }); - test('returns set value of randomize integer', function() { - let randVal = Math.floor(Math.random()*120); + test('returns set value of randomize integer', function () { + let randVal = Math.floor(Math.random() * 120); myp5.frameRate(randVal); assert.strictEqual(myp5.getTargetFrameRate(), randVal); }); }); - suite('Canvas dimensions', function() { - test('p5.prototype.width', function() { + suite('Canvas dimensions', function () { + test('p5.prototype.width', function () { myp5.createCanvas(20, 30); assert.strictEqual(myp5.width, 20); }); - test('p5.prototype.height', function() { + test('p5.prototype.height', function () { myp5.createCanvas(20, 30); assert.strictEqual(myp5.height, 30); }); }); - suite('p5.prototype.pixelDensity', function() { - test('returns the pixel density', function() { + suite('p5.prototype.pixelDensity', function () { + test('returns the pixel density', function () { assert.isNumber(myp5.pixelDensity()); }); - test('sets the pixel density', function() { + test('sets the pixel density', function () { myp5.pixelDensity(2); assert.strictEqual(myp5.pixelDensity(), 2); }); }); - suite('p5.prototype.displayDensity', function() { - test('returns the pixel density of the display', function() { + suite('p5.prototype.displayDensity', function () { + test('returns the pixel density of the display', function () { assert.isNumber(myp5.displayDensity()); }); - test('pixelDensity does not change display density', function() { + test('pixelDensity does not change display density', function () { let pd = myp5.displayDensity(); myp5.pixelDensity(pd + 1); assert.isNumber(myp5.displayDensity(), pd); }); }); - suite('2D context test', function() { - beforeEach(function() { + suite('2D context test', function () { + beforeEach(function () { myp5.createCanvas(100, 100); }); - test('worldToScreen for 2D context', function() { + test('worldToScreen for 2D context', function () { let worldPos = myp5.createVector(50, 50); let screenPos = myp5.worldToScreen(worldPos); assert.closeTo(screenPos.x, 50, 0.1); assert.closeTo(screenPos.y, 50, 0.1); }); - test('worldToScreen accepts numbers with translation in 2D', function() { + test('worldToScreen accepts numbers with translation in 2D', function () { myp5.push(); myp5.translate(50, 50); let screenPos = myp5.worldToScreen(0, 0); @@ -258,7 +267,7 @@ suite('Environment', function() { assert.closeTo(screenPos.y, 50, 0.1); }); - test('worldToScreen with rotation in 2D', function() { + test('worldToScreen with rotation in 2D', function () { myp5.push(); myp5.translate(50, 50); myp5.rotate(myp5.PI / 2); @@ -269,14 +278,14 @@ suite('Environment', function() { assert.closeTo(screenPos.y, 60, 0.1); }); - test('screenToWorld for 2D context', function() { + test('screenToWorld for 2D context', function () { let screenPos = myp5.createVector(50, 50); let worldPos = myp5.screenToWorld(screenPos); assert.closeTo(worldPos.x, 50, 0.1); assert.closeTo(worldPos.y, 50, 0.1); }); - test('screenToWorld accepts numbers with translation in 2D', function() { + test('screenToWorld accepts numbers with translation in 2D', function () { myp5.push(); myp5.translate(50, 50); let worldPos = myp5.screenToWorld(50, 50); @@ -285,7 +294,7 @@ suite('Environment', function() { assert.closeTo(worldPos.y, 0, 0.1); }); - test('screenToWorld with rotation in 2D', function() { + test('screenToWorld with rotation in 2D', function () { myp5.push(); myp5.translate(50, 50); myp5.rotate(myp5.PI / 2); @@ -296,7 +305,7 @@ suite('Environment', function() { assert.closeTo(worldPos.y, 0, 0.1); }); - test('screenToWorld is inverse of worldToScreen in 2D', function() { + test('screenToWorld is inverse of worldToScreen in 2D', function () { myp5.push(); // Arbitrary rotation myp5.translate(10, 10); @@ -310,19 +319,19 @@ suite('Environment', function() { }); }); - suite('3D context test', function() { - beforeEach(function() { + suite('3D context test', function () { + beforeEach(function () { myp5.createCanvas(100, 100, myp5.WEBGL); }); - test('worldToScreen for 3D context', function() { + test('worldToScreen for 3D context', function () { let worldPos = myp5.createVector(0, 0, 0); let screenPos = myp5.worldToScreen(worldPos); assert.closeTo(screenPos.x, 50, 0.1); assert.closeTo(screenPos.y, 50, 0.1); }); - test('worldToScreen accepts numbers with translation in 3D', function() { + test('worldToScreen accepts numbers with translation in 3D', function () { myp5.push(); myp5.translate(50, 50, 0); let screenPos = myp5.worldToScreen(0, 0, 0); @@ -331,7 +340,7 @@ suite('Environment', function() { assert.closeTo(screenPos.y, 100, 0.1); }); - test('worldToScreen with rotation in 3D around Y-axis', function() { + test('worldToScreen with rotation in 3D around Y-axis', function () { myp5.push(); myp5.rotateY(myp5.PI / 2); let worldPos = myp5.createVector(50, 0, 0); @@ -341,7 +350,7 @@ suite('Environment', function() { assert.closeTo(screenPos.y, 50, 0.1); }); - test('worldToScreen with rotation in 3D around Z-axis', function() { + test('worldToScreen with rotation in 3D around Z-axis', function () { myp5.push(); myp5.rotateZ(myp5.PI / 2); let worldPos = myp5.createVector(10, 0, 0); @@ -351,7 +360,7 @@ suite('Environment', function() { assert.closeTo(screenPos.y, 60, 0.1); }); - test('screenToWorld for 3D context', function() { + test('screenToWorld for 3D context', function () { let screenPos = myp5.createVector(50, 50); let worldPos = myp5.screenToWorld(screenPos); assert.closeTo(worldPos.x, 0, 0.1); @@ -359,7 +368,7 @@ suite('Environment', function() { assert.closeTo(worldPos.z, 0, 0.1); }); - test('screenToWorld accepts numbers with translation in 3D', function() { + test('screenToWorld accepts numbers with translation in 3D', function () { myp5.push(); myp5.translate(50, 50); let worldPos = myp5.screenToWorld(100, 100); @@ -368,7 +377,7 @@ suite('Environment', function() { assert.closeTo(worldPos.y, 0, 0.1); }); - test('screenToWorld with rotation in 3D around Y-axis', function() { + test('screenToWorld with rotation in 3D around Y-axis', function () { myp5.push(); myp5.rotateY(myp5.PI / 2); let screenPos = myp5.createVector(50, 50); @@ -377,7 +386,7 @@ suite('Environment', function() { assert.closeTo(worldPos.y, 0, 0.1); }); - test('screenToWorld with rotation in 3D around Z-axis', function() { + test('screenToWorld with rotation in 3D around Z-axis', function () { myp5.push(); myp5.rotateZ(myp5.PI / 2); let screenPos = myp5.createVector(50, 60); @@ -387,7 +396,7 @@ suite('Environment', function() { assert.closeTo(worldPos.y, 0, 0.1); }); - test('screenToWorld is inverse of worldToScreen in 3D', function() { + test('screenToWorld is inverse of worldToScreen in 3D', function () { myp5.push(); // Arbitrary rotation myp5.translate(10, 10); diff --git a/test/unit/core/main.js b/test/unit/core/main.js index 84e9eb00da..6421b3f090 100644 --- a/test/unit/core/main.js +++ b/test/unit/core/main.js @@ -1,5 +1,9 @@ import p5 from '../../../src/app.js'; -import { createP5Iframe, P5_SCRIPT_TAG, P5_SCRIPT_URL } from '../../js/p5_helpers'; +import { + createP5Iframe, + P5_SCRIPT_TAG, + P5_SCRIPT_URL +} from '../../js/p5_helpers'; import { vi } from 'vitest'; suite('Core', function () { @@ -105,9 +109,7 @@ suite('Core', function () { try { globalObject.text = 'hi'; bind('text', noop); - expect( - _friendlyErrorStub - ).toHaveBeenCalledTimes(1); + expect(_friendlyErrorStub).toHaveBeenCalledTimes(1); } finally { vi.restoreAllMocks(); } diff --git a/test/unit/core/p5.Graphics.js b/test/unit/core/p5.Graphics.js index 00961911de..b24e1fd80f 100644 --- a/test/unit/core/p5.Graphics.js +++ b/test/unit/core/p5.Graphics.js @@ -1,18 +1,18 @@ import p5 from '../../../src/app.js'; import { vi } from 'vitest'; -suite('Graphics', function() { +suite('Graphics', function () { var myp5; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); @@ -44,16 +44,15 @@ suite('Graphics', function() { assert.strictEqual(real, expected, 'invalid number of pixels'); } - suite('p5.prototype.createGraphics', function() { - test('it creates a graphics', function() { + suite('p5.prototype.createGraphics', function () { + test('it creates a graphics', function () { var graph = myp5.createGraphics(10, 17); assert.isObject(graph); }); - }); - suite('p5.Graphics', function() { - test('it has necessary properties', function() { + suite('p5.Graphics', function () { + test('it has necessary properties', function () { var graph = myp5.createGraphics(10, 17); assert.property(graph, 'width'); assert.property(graph, 'height'); @@ -63,27 +62,27 @@ suite('Graphics', function() { assert.property(graph, 'pixels'); }); - test('it has consistent sizes', function() { + test('it has consistent sizes', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(1); assertValidGraphSizes(graph, 10, 17, 1); }); - test('its canvas has consistent sizes', function() { + test('its canvas has consistent sizes', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(1); assertValidCanvasSizes(graph.canvas, 10, 17, 1); }); - test('it has a valid pixels array', function() { + test('it has a valid pixels array', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(1); assertValidPixels(graph, 10, 17, 1); }); }); - suite('p5.Graphics.pixelDensity', function() { - test('it can change density', function() { + suite('p5.Graphics.pixelDensity', function () { + test('it can change density', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(1); assert.strictEqual(graph.pixelDensity(), 1); @@ -91,7 +90,7 @@ suite('Graphics', function() { assert.strictEqual(graph.pixelDensity(), 3); }); - test('it keeps valid sizes after change', function() { + test('it keeps valid sizes after change', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(1); assertValidGraphSizes(graph, 10, 17, 1); @@ -99,7 +98,7 @@ suite('Graphics', function() { assertValidGraphSizes(graph, 10, 17, 3); }); - test('its canvas keeps valid sizes after change', function() { + test('its canvas keeps valid sizes after change', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(1); assertValidCanvasSizes(graph.canvas, 10, 17, 1); @@ -107,7 +106,7 @@ suite('Graphics', function() { assertValidCanvasSizes(graph.canvas, 10, 17, 3); }); - test('it keeps a valid pixel array after change', function() { + test('it keeps a valid pixel array after change', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(1); assertValidPixels(graph, 10, 17, 1); @@ -117,15 +116,15 @@ suite('Graphics', function() { }); }); - suite('p5.Graphics.prototype.splineVertex', function() { - test('should be a function in graphics', function() { + suite('p5.Graphics.prototype.splineVertex', function () { + test('should be a function in graphics', function () { let g = myp5.createGraphics(10, 10); assert.ok(g.splineVertex); assert.typeOf(g.splineVertex, 'function'); }); }); - suite('p5.Graphics.resizeCanvas', function() { + suite('p5.Graphics.resizeCanvas', function () { let glStub; afterEach(() => { @@ -135,50 +134,50 @@ suite('Graphics', function() { } }); - test('it can call resizeCanvas', function() { + test('it can call resizeCanvas', function () { var graph = myp5.createGraphics(10, 17); - var resize = function() { + var resize = function () { graph.resizeCanvas(19, 16); }; assert.doesNotThrow(resize); }); - test('it resizes properly with pixelDensity 1', function() { + test('it resizes properly with pixelDensity 1', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(1); graph.resizeCanvas(19, 16); assertValidGraphSizes(graph, 19, 16, 1); }); - test('its canvas resizes properly with pixelDensity 1', function() { + test('its canvas resizes properly with pixelDensity 1', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(1); graph.resizeCanvas(19, 16); assertValidCanvasSizes(graph.canvas, 19, 16, 1); }); - test('it resizes properly the pixels array with density 1', function() { + test('it resizes properly the pixels array with density 1', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(1); graph.resizeCanvas(19, 16); assertValidPixels(graph, 19, 16, 1); }); - test('it resizes properly with pixelDensity 2', function() { + test('it resizes properly with pixelDensity 2', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(2); graph.resizeCanvas(19, 16); assertValidGraphSizes(graph, 19, 16, 2); }); - test('its canvas resizes properly with pixelDensity 2', function() { + test('its canvas resizes properly with pixelDensity 2', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(2); graph.resizeCanvas(19, 16); assertValidCanvasSizes(graph.canvas, 19, 16, 2); }); - test('it resizes properly the pixels array with density 2', function() { + test('it resizes properly the pixels array with density 2', function () { var graph = myp5.createGraphics(10, 17); graph.pixelDensity(2); graph.resizeCanvas(19, 16); @@ -186,7 +185,7 @@ suite('Graphics', function() { }); // NOTE: check this - test('it resizes the graphics based on max texture size', function() { + test('it resizes the graphics based on max texture size', function () { glStub = vi.spyOn(p5.RendererGL.prototype, '_getMaxTextureSize'); const fakeMaxTextureSize = 100; glStub.mockReturnValue(fakeMaxTextureSize); @@ -196,8 +195,8 @@ suite('Graphics', function() { }); }); - suite('p5.Graphics.remove()', function() { - test('it sets properties to undefined after removal', function() { + suite('p5.Graphics.remove()', function () { + test('it sets properties to undefined after removal', function () { var graph = myp5.createGraphics(10, 17); graph.remove(); assert.isUndefined(graph.canvas); diff --git a/test/unit/core/param_errors.js b/test/unit/core/param_errors.js index 55cd19cf58..d46c37a70b 100644 --- a/test/unit/core/param_errors.js +++ b/test/unit/core/param_errors.js @@ -7,29 +7,29 @@ suite('Validate Params', function () { Color: function () { return 'mock p5.Color'; }, - Image: function() { + Image: function () { return 'mock p5.Image'; }, - Element: function() { + Element: function () { return 'mock p5.Element'; }, - Texture: function() { + Texture: function () { return 'mock p5.Texture'; }, - Framebuffer: function() { + Framebuffer: function () { return 'mock p5.Framebuffer'; }, - FramebufferTexture: function() { + FramebufferTexture: function () { return 'mock p5.FramebufferTexture'; }, - Renderer: function() { + Renderer: function () { return 'mock p5.Renderer'; }, - Graphics: function() { + Graphics: function () { return 'mock p5.Graphics'; }, _error: () => {}, - registerDecorator: () => {}, + registerDecorator: () => {} }; const mockP5Prototype = {}; @@ -37,37 +37,45 @@ suite('Validate Params', function () { validateParams(mockP5, mockP5Prototype, {}); }); - afterAll(function () { - }); + afterAll(function () {}); - suite('validateParams: multiple types allowed for single parameter', function () { - test('saturation(): valid inputs', () => { - const validInputs = [ - { input: ['rgb(255, 128, 128)'] }, - { input: [[0, 50, 100]] }, - { input: [new mockP5.Color()] } - ]; + suite( + 'validateParams: multiple types allowed for single parameter', + function () { + test('saturation(): valid inputs', () => { + const validInputs = [ + { input: ['rgb(255, 128, 128)'] }, + { input: [[0, 50, 100]] }, + { input: [new mockP5.Color()] } + ]; - validInputs.forEach(({ input }) => { - const result = mockP5Prototype._validate('saturation', input); - assert.isTrue(result.success); + validInputs.forEach(({ input }) => { + const result = mockP5Prototype._validate('saturation', input); + assert.isTrue(result.success); + }); }); - }); - test('saturation(): invalid inputs', () => { - const invalidInputs = [ - { input: [true] }, - { input: [42] }, - { input: [{}] }, - { input: [null] } - ]; + test('saturation(): invalid inputs', () => { + const invalidInputs = [ + { input: [true] }, + { input: [42] }, + { input: [{}] }, + { input: [null] } + ]; - invalidInputs.forEach(({ input }) => { - const result = mockP5Prototype._validate('p5.saturation', input); - assert.isTrue(result.error.toString().startsWith('Expected Color or array or string at the first parameter')); + invalidInputs.forEach(({ input }) => { + const result = mockP5Prototype._validate('p5.saturation', input); + assert.isTrue( + result.error + .toString() + .startsWith( + 'Expected Color or array or string at the first parameter' + ) + ); + }); }); - }); - }); + } + ); suite('validateParams: constant as parameter', function () { const validInputs = [ @@ -91,7 +99,10 @@ suite('Validate Params', function () { invalidInputs.forEach(({ name, input }) => { test(`blendMode(): ${name}`, () => { const result = mockP5Prototype._validate('p5.blendMode', [input]); - const expectedError = 'Expected constant (please refer to documentation for allowed values) at the first parameter, but received ' + input + ' in p5.blendMode().'; + const expectedError = + 'Expected constant (please refer to documentation for allowed values) at the first parameter, but received ' + + input + + ' in p5.blendMode().'; assert.equal(result.error.toString(), expectedError); }); }); @@ -99,8 +110,14 @@ suite('Validate Params', function () { suite('validateParams: numbers + optional constant for arc()', function () { const validInputs = [ - { name: 'no friendly-err-msg', input: [200, 100, 100, 80, 0, Math.PI, constants.PIE, 30] }, - { name: 'missing optional param #6 & #7, no friendly-err-msg', input: [200, 100, 100, 80, 0, Math.PI] } + { + name: 'no friendly-err-msg', + input: [200, 100, 100, 80, 0, Math.PI, constants.PIE, 30] + }, + { + name: 'missing optional param #6 & #7, no friendly-err-msg', + input: [200, 100, 100, 80, 0, Math.PI] + } ]; validInputs.forEach(({ name, input }) => { test(`arc(): ${name}`, () => { @@ -110,11 +127,31 @@ suite('Validate Params', function () { }); const invalidInputs = [ - { name: 'missing required arc parameters #4, #5', input: [200, 100, 100, 80], msg: 'Expected at least 6 arguments, but received fewer in p5.arc(). For more information, see https://p5js.org/reference/p5/arc.' }, - { name: 'missing required param #0', input: [undefined, 100, 100, 80, 0, Math.PI, constants.PIE, 30], msg: 'Expected number at the first parameter, but received undefined in p5.arc().' }, - { name: 'missing required param #4', input: [200, 100, 100, 80, undefined, 0], msg: 'Expected number at the fifth parameter, but received undefined in p5.arc().' }, - { name: 'missing optional param #5', input: [200, 100, 100, 80, 0, undefined, Math.PI], msg: 'Expected number at the sixth parameter, but received undefined in p5.arc().' }, - { name: 'wrong param type at #0', input: ['a', 100, 100, 80, 0, Math.PI, constants.PIE, 30], msg: 'Expected number at the first parameter, but received string in p5.arc().' } + { + name: 'missing required arc parameters #4, #5', + input: [200, 100, 100, 80], + msg: 'Expected at least 6 arguments, but received fewer in p5.arc(). For more information, see https://p5js.org/reference/p5/arc.' + }, + { + name: 'missing required param #0', + input: [undefined, 100, 100, 80, 0, Math.PI, constants.PIE, 30], + msg: 'Expected number at the first parameter, but received undefined in p5.arc().' + }, + { + name: 'missing required param #4', + input: [200, 100, 100, 80, undefined, 0], + msg: 'Expected number at the fifth parameter, but received undefined in p5.arc().' + }, + { + name: 'missing optional param #5', + input: [200, 100, 100, 80, 0, undefined, Math.PI], + msg: 'Expected number at the sixth parameter, but received undefined in p5.arc().' + }, + { + name: 'wrong param type at #0', + input: ['a', 100, 100, 80, 0, Math.PI, constants.PIE, 30], + msg: 'Expected number at the first parameter, but received string in p5.arc().' + } ]; invalidInputs.forEach(({ name, input, msg }) => { @@ -127,7 +164,11 @@ suite('Validate Params', function () { suite('validateParams: promise where no promise is expected', function () { test('image(): promise for first argument', function () { - const result = mockP5Prototype._validate('p5.image', [Promise.resolve(), 0, 0]); + const result = mockP5Prototype._validate('p5.image', [ + Promise.resolve(), + 0, + 0 + ]); assert.equal( result.error.toString(), 'Did you mean to put `await` before a loading function? An unexpected Promise was found. Expected Image or Element or Texture or Framebuffer or FramebufferTexture or Renderer or Graphics at the first parameter in p5.image().' @@ -137,20 +178,57 @@ suite('Validate Params', function () { suite('validateParams: class, multi-types + optional numbers', function () { test('ambientLight(): no firendly-err-msg', function () { - const result = mockP5Prototype._validate('p5.ambientLight', [new mockP5.Color()]); + const result = mockP5Prototype._validate('p5.ambientLight', [ + new mockP5.Color() + ]); assert.isTrue(result.success); }); }); suite('validateParams: a few edge cases', function () { const invalidInputs = [ - { fn: 'color', name: 'wrong type for optional parameter', input: [0, 0, 0, 'A'], msg: 'Expected number at the fourth parameter, but received string in p5.color().' }, - { fn: 'color', name: 'superfluous parameter', input: [[0, 0, 0], 0], msg: 'Expected number at the first parameter, but received array in p5.color().' }, - { fn: 'color', name: 'wrong element types', input: [['A', 'B', 'C']], msg: 'Expected number at the first parameter, but received array in p5.color().' }, - { fn: 'rect', name: 'null, non-trailing, optional parameter', input: [0, 0, 0, 0, null, 0, 0, 0], msg: 'Expected number at the fifth parameter, but received null in p5.rect().' }, - { fn: 'color', name: 'too many args + wrong types too', input: ['A', 'A', 0, 0, 0, 0, 0, 0, 0, 0], msg: 'Expected at most 4 arguments, but received more in p5.color(). For more information, see https://p5js.org/reference/p5/color.' }, - { fn: 'line', name: 'null string given', input: [1, 2, 4, 'null'], msg: 'Expected number at the fourth parameter, but received string in p5.line().' }, - { fn: 'line', name: 'NaN value given', input: [1, 2, 4, NaN], msg: 'Expected number at the fourth parameter, but received NaN in p5.line().' } + { + fn: 'color', + name: 'wrong type for optional parameter', + input: [0, 0, 0, 'A'], + msg: 'Expected number at the fourth parameter, but received string in p5.color().' + }, + { + fn: 'color', + name: 'superfluous parameter', + input: [[0, 0, 0], 0], + msg: 'Expected number at the first parameter, but received array in p5.color().' + }, + { + fn: 'color', + name: 'wrong element types', + input: [['A', 'B', 'C']], + msg: 'Expected number at the first parameter, but received array in p5.color().' + }, + { + fn: 'rect', + name: 'null, non-trailing, optional parameter', + input: [0, 0, 0, 0, null, 0, 0, 0], + msg: 'Expected number at the fifth parameter, but received null in p5.rect().' + }, + { + fn: 'color', + name: 'too many args + wrong types too', + input: ['A', 'A', 0, 0, 0, 0, 0, 0, 0, 0], + msg: 'Expected at most 4 arguments, but received more in p5.color(). For more information, see https://p5js.org/reference/p5/color.' + }, + { + fn: 'line', + name: 'null string given', + input: [1, 2, 4, 'null'], + msg: 'Expected number at the fourth parameter, but received string in p5.line().' + }, + { + fn: 'line', + name: 'NaN value given', + input: [1, 2, 4, NaN], + msg: 'Expected number at the fourth parameter, but received NaN in p5.line().' + } ]; invalidInputs.forEach(({ name, input, fn, msg }) => { @@ -163,12 +241,27 @@ suite('Validate Params', function () { suite('validateParams: trailing undefined arguments', function () { const invalidInputs = [ - { fn: 'color', name: 'missing params #1, #2', input: [12, undefined, undefined], msg: 'Expected number at the second parameter, but received undefined in p5.color().' }, + { + fn: 'color', + name: 'missing params #1, #2', + input: [12, undefined, undefined], + msg: 'Expected number at the second parameter, but received undefined in p5.color().' + }, // Even though the undefined arguments are technically allowed for // optional parameters, it is more likely that the user wanted to call // the function with meaningful arguments. - { fn: 'random', name: 'missing params #0, #1', input: [undefined, undefined], msg: 'All arguments for p5.random() are undefined. There is likely an error in the code.' }, - { fn: 'circle', name: 'missing compulsory parameter #2', input: [5, 5, undefined], msg: 'Expected number at the third parameter, but received undefined in p5.circle().' } + { + fn: 'random', + name: 'missing params #0, #1', + input: [undefined, undefined], + msg: 'All arguments for p5.random() are undefined. There is likely an error in the code.' + }, + { + fn: 'circle', + name: 'missing compulsory parameter #2', + input: [5, 5, undefined], + msg: 'Expected number at the third parameter, but received undefined in p5.circle().' + } ]; invalidInputs.forEach(({ fn, name, input, msg }) => { @@ -193,10 +286,26 @@ suite('Validate Params', function () { }); const invalidInputs = [ - { name: 'optional parameter, incorrect type', input: [65, 100, 100, 'a'], msg: 'Expected number at the fourth parameter, but received string in p5.color().' }, - { name: 'extra parameter', input: [[65, 100, 100], 100], msg: 'Expected number at the first parameter, but received array in p5.color().' }, - { name: 'incorrect element type', input: ['A', 'B', 'C'], msg: 'Expected number at the first parameter, but received string in p5.color().' }, - { name: 'incorrect parameter count', input: ['A', 'A', 0, 0, 0, 0, 0, 0], msg: 'Expected at most 4 arguments, but received more in p5.color(). For more information, see https://p5js.org/reference/p5/color.' } + { + name: 'optional parameter, incorrect type', + input: [65, 100, 100, 'a'], + msg: 'Expected number at the fourth parameter, but received string in p5.color().' + }, + { + name: 'extra parameter', + input: [[65, 100, 100], 100], + msg: 'Expected number at the first parameter, but received array in p5.color().' + }, + { + name: 'incorrect element type', + input: ['A', 'B', 'C'], + msg: 'Expected number at the first parameter, but received string in p5.color().' + }, + { + name: 'incorrect parameter count', + input: ['A', 'A', 0, 0, 0, 0, 0, 0], + msg: 'Expected at most 4 arguments, but received more in p5.color(). For more information, see https://p5js.org/reference/p5/color.' + } ]; invalidInputs.forEach(({ name, input, msg }) => { @@ -223,17 +332,29 @@ suite('Validate Params', function () { test('set() with Boolean (invalid)', function () { const result = mockP5Prototype._validate('p5.set', [0, 0, true]); - assert.equal(result.error.toString(), 'Expected number or array or object at the third parameter, but received boolean in p5.set().'); + assert.equal( + result.error.toString(), + 'Expected number or array or object at the third parameter, but received boolean in p5.set().' + ); }); }); - suite('validateParams: web API objects', function () { // TODO: fix this p5 error + suite('validateParams: web API objects', function () { + // TODO: fix this p5 error const audioContext = new AudioContext(); const gainNode = audioContext.createGain(); const testCases = [ - { fn: 'mouseMoved', name: 'no friendly-err-msg', input: [new MouseEvent('click')] }, - { fn: 'p5.MediaElement.connect', name: 'no friendly-err-msg', input: [gainNode] } + { + fn: 'mouseMoved', + name: 'no friendly-err-msg', + input: [new MouseEvent('click')] + }, + { + fn: 'p5.MediaElement.connect', + name: 'no friendly-err-msg', + input: [gainNode] + } ]; testCases.forEach(({ fn, name, input }) => { @@ -251,30 +372,38 @@ suite('Validate Params', function () { [new mockP5.Color(), 0.8], [new mockP5.Color(), 0.5] ]; - const result = mockP5Prototype._validate('p5.paletteLerp', [colorStops, 0.5]); + const result = mockP5Prototype._validate('p5.paletteLerp', [ + colorStops, + 0.5 + ]); assert.isTrue(result.success); }); }); suite('validateParams: rest arguments', function () { - test('createVector(): works with no args', function() { + test('createVector(): works with no args', function () { const result = mockP5Prototype._validate('p5.createVector', []); assert.isTrue(result.success); }); - test('createVector(): works with one number', function() { + test('createVector(): works with one number', function () { const result = mockP5Prototype._validate('p5.createVector', [1]); assert.isTrue(result.success); }); - test('createVector(): works with many numbers', function() { + test('createVector(): works with many numbers', function () { const result = mockP5Prototype._validate('p5.createVector', [1, 2, 3, 4]); assert.isTrue(result.success); }); - test('createVector(): fails with a non-number', function() { + test('createVector(): fails with a non-number', function () { const result = mockP5Prototype._validate('p5.createVector', ['1']); assert.isFalse(result.success); }); - test('createVector(): fails with any non-number', function() { - const result = mockP5Prototype._validate('p5.createVector', [1, 2, '3', 4]); + test('createVector(): fails with any non-number', function () { + const result = mockP5Prototype._validate('p5.createVector', [ + 1, + 2, + '3', + 4 + ]); assert.isFalse(result.success); }); }); diff --git a/test/unit/core/properties.js b/test/unit/core/properties.js index e7def6900e..2150ba0a42 100644 --- a/test/unit/core/properties.js +++ b/test/unit/core/properties.js @@ -1,10 +1,9 @@ import p5 from '../../../src/app.js'; -suite('Set/get properties', function() { - +suite('Set/get properties', function () { let p = new p5(function (sketch) { - sketch.setup = function () { }; - sketch.draw = function () { }; + sketch.setup = function () {}; + sketch.draw = function () {}; }); let getters = { @@ -49,20 +48,21 @@ suite('Set/get properties', function() { Object.keys(getters).forEach(prop => { let arg = getters[prop]; - test(`${prop}()`, function() { - + test(`${prop}()`, function () { // setter if (typeof arg === 'object' && !(arg instanceof p5.Color)) { p[prop](...Object.values(arg)); // set with object - } - else if (Array.isArray(arg)) { + } else if (Array.isArray(arg)) { p[prop](...arg); // set with array - } - else { + } else { p[prop](arg); // set with primitive or p5.Color } // getter - assert.strictEqual(p[prop]().toString(), arg.toString(), `${arg.toString()}`); + assert.strictEqual( + p[prop]().toString(), + arg.toString(), + `${arg.toString()}` + ); }); }); }); diff --git a/test/unit/core/rendering.js b/test/unit/core/rendering.js index 7ffd6babf7..dec2839e6d 100644 --- a/test/unit/core/rendering.js +++ b/test/unit/core/rendering.js @@ -1,30 +1,30 @@ import p5 from '../../../src/app.js'; import { vi, expect } from 'vitest'; -suite('Rendering', function() { +suite('Rendering', function () { var myp5; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('p5.prototype.webglVersion', function() { - test('should return P2D if not using WebGL at all', function() { + suite('p5.prototype.webglVersion', function () { + test('should return P2D if not using WebGL at all', function () { myp5.createCanvas(10, 10); assert.equal(myp5.webglVersion, myp5.P2D); }); }); - suite('p5.prototype.createCanvas', function() { - test('should have correct initial colors', function() { + suite('p5.prototype.createCanvas', function () { + test('should have correct initial colors', function () { var white = myp5.color(255, 255, 255)._array; var black = myp5.color(0, 0, 0)._array; assert.deepEqual(myp5.color(myp5._renderer._getFill())._array, white); @@ -45,7 +45,7 @@ suite('Rendering', function() { }); }); - suite('p5.prototype.resizeCanvas', function() { + suite('p5.prototype.resizeCanvas', function () { let glStub; afterEach(() => { @@ -55,37 +55,37 @@ suite('Rendering', function() { } }); - test('should resize canvas', function() { + test('should resize canvas', function () { myp5.resizeCanvas(10, 20); assert.equal(myp5.canvas.width, 10 * myp5.pixelDensity()); assert.equal(myp5.canvas.height, 20 * myp5.pixelDensity()); }); - test('should restore fill color', function() { + test('should restore fill color', function () { myp5.fill('#f0f0f0'); myp5.resizeCanvas(10, 10); assert.equal(myp5.drawingContext.fillStyle, '#f0f0f0'); }); - test('should restore stroke color', function() { + test('should restore stroke color', function () { myp5.stroke('#f0f0f0'); myp5.resizeCanvas(10, 10); assert.equal(myp5.drawingContext.strokeStyle, '#f0f0f0'); }); - test('should restore stroke weight', function() { + test('should restore stroke weight', function () { myp5.strokeWeight(323); myp5.resizeCanvas(10, 10); assert.equal(myp5.drawingContext.lineWidth, 323); }); - test('should restore stroke cap', function() { + test('should restore stroke cap', function () { myp5.strokeCap(myp5.PROJECT); myp5.resizeCanvas(10, 10); assert.equal(myp5.drawingContext.lineCap, myp5.PROJECT); }); - test.todo('should resize framebuffers', function() { + test.todo('should resize framebuffers', function () { myp5.createCanvas(10, 10, myp5.WEBGL); const fbo = myp5.createFramebuffer(); myp5.resizeCanvas(5, 15); @@ -93,7 +93,7 @@ suite('Rendering', function() { assert.equal(fbo.height, 15); }); - test.todo('should resize graphic framebuffers', function() { + test.todo('should resize graphic framebuffers', function () { const graphic = myp5.createGraphics(10, 10, myp5.WEBGL); const fbo = graphic.createFramebuffer(); graphic.resizeCanvas(5, 15); @@ -101,7 +101,7 @@ suite('Rendering', function() { assert.equal(fbo.height, 15); }); - test('should limit dimensions of canvas based on max texture size on create', function() { + test('should limit dimensions of canvas based on max texture size on create', function () { glStub = vi.spyOn(p5.RendererGL.prototype, '_getMaxTextureSize'); const fakeMaxTextureSize = 100; glStub.mockReturnValue(fakeMaxTextureSize); @@ -112,7 +112,7 @@ suite('Rendering', function() { assert.equal(myp5.height, 100); }); - test('should limit dimensions of canvas based on max texture size on resize', function() { + test('should limit dimensions of canvas based on max texture size on resize', function () { glStub = vi.spyOn(p5.RendererGL.prototype, '_getMaxTextureSize'); const fakeMaxTextureSize = 100; glStub.mockReturnValue(fakeMaxTextureSize); @@ -125,65 +125,88 @@ suite('Rendering', function() { }); }); - suite('p5.prototype.blendMode', function() { - var drawX = function() { + suite('p5.prototype.blendMode', function () { + var drawX = function () { myp5.strokeWeight(30); myp5.stroke(80, 150, 255); myp5.line(25, 25, 75, 75); myp5.stroke(255, 50, 50); myp5.line(75, 25, 25, 75); }; - test('should be a function', function() { + test('should be a function', function () { assert.ok(myp5.blendMode); assert.typeOf(myp5.blendMode, 'function'); }); - test.todo('should be able to ADD', function() { + test.todo('should be able to ADD', function () { myp5.blendMode(myp5.ADD); drawX(); }); - test.todo('should be able to MULTIPLY', function() { + test.todo('should be able to MULTIPLY', function () { myp5.blendMode(myp5.MULTIPLY); drawX(); }); }); - suite('p5.prototype.setAttributes', function() { - test.todo('_glAttributes should be null at start', function() { + suite('p5.prototype.setAttributes', function () { + test.todo('_glAttributes should be null at start', function () { assert.deepEqual(myp5._glAttributes, null); }); - test('_glAttributes should modify with setAttributes', function() { + test('_glAttributes should modify with setAttributes', function () { myp5.setAttributes({ antialias: false, perPixelLighting: true }); assert.deepEqual(myp5._glAttributes.antialias, false); assert.deepEqual(myp5._glAttributes.perPixelLighting, true); }); - test('_glAttributes.antialias modify with smooth()', function() { + test('_glAttributes.antialias modify with smooth()', function () { myp5.smooth(); assert.deepEqual(myp5._glAttributes.antialias, true); }); }); var webglMethods = [ - 'rotateX', 'rotateY', 'rotateZ', - 'camera', 'perspective', 'ortho', 'frustum', 'orbitControl', - 'ambientLight', 'directionalLight', 'pointLight', 'lights', 'specularColor', 'spotLight', + 'rotateX', + 'rotateY', + 'rotateZ', + 'camera', + 'perspective', + 'ortho', + 'frustum', + 'orbitControl', + 'ambientLight', + 'directionalLight', + 'pointLight', + 'lights', + 'specularColor', + 'spotLight', 'model', 'shader', - 'normalMaterial', 'texture', 'ambientMaterial', 'emissiveMaterial', 'specularMaterial', - 'shininess', 'lightFalloff', 'metalness', - 'plane', 'box', 'sphere', 'cylinder', 'cone', 'ellipsoid', 'torus' + 'normalMaterial', + 'texture', + 'ambientMaterial', + 'emissiveMaterial', + 'specularMaterial', + 'shininess', + 'lightFalloff', + 'metalness', + 'plane', + 'box', + 'sphere', + 'cylinder', + 'cone', + 'ellipsoid', + 'torus' ]; - suite.todo('webgl assertions', function() { + suite.todo('webgl assertions', function () { for (var i = 0; i < webglMethods.length; i++) { var webglMethod = webglMethods[i]; test( webglMethod + '() should throw a WEBGL assertion Error', - (function(webglMethod) { - return function() { + (function (webglMethod) { + return function () { var validateParamters = myp5.validateParameters; myp5.validateParameters = false; try { - expect(function() { + expect(function () { myp5[webglMethod].call(myp5); }).toThrow(Error, /is only supported in WEBGL mode/); } finally { @@ -195,37 +218,37 @@ suite('Rendering', function() { } }); - suite('spline functions', function() { - test('setting via splineProperty()', function() { + suite('spline functions', function () { + test('setting via splineProperty()', function () { myp5.splineProperty('tightness', 2); expect(myp5.splineProperty('tightness')).toEqual(2); expect(myp5.splineProperties()).toMatchObject({ tightness: 2 }); }); - test('setting via splineProperties()', function() { + test('setting via splineProperties()', function () { myp5.splineProperties({ tightness: 2 }); expect(myp5.splineProperty('tightness')).toEqual(2); expect(myp5.splineProperties()).toMatchObject({ tightness: 2 }); }); }); - suite('set() with p5.Graphics', function() { + suite('set() with p5.Graphics', function () { let myp5; - beforeEach(function() { - myp5 = new p5(function(p) { - p.setup = function() { + beforeEach(function () { + myp5 = new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100); p.pixelDensity(1); }; }); }); - afterEach(function() { + afterEach(function () { myp5.remove(); }); - test('set() works with p5.Graphics on main canvas', function() { + test('set() works with p5.Graphics on main canvas', function () { myp5.background(0, 255, 0); const gfx = myp5.createGraphics(20, 20); gfx.background(255, 0, 0); @@ -235,7 +258,7 @@ suite('Rendering', function() { assert.deepEqual(myp5.get(5, 5), [0, 255, 0, 255]); }); - test('set() works with p5.Graphics on p5.Graphics', function() { + test('set() works with p5.Graphics on p5.Graphics', function () { const mainGfx = myp5.createGraphics(100, 100); mainGfx.background(255); const smallGfx = myp5.createGraphics(20, 20); @@ -246,7 +269,7 @@ suite('Rendering', function() { assert.deepEqual(myp5.get(15, 15), [0, 0, 255, 255]); }); - test('set() clears area under p5.Graphics', function() { + test('set() clears area under p5.Graphics', function () { myp5.background(255); myp5.stroke(255, 0, 0); myp5.strokeWeight(2); @@ -260,7 +283,7 @@ suite('Rendering', function() { assert.deepEqual(myp5.get(35, 35), [0, 255, 0, 255]); }); - test('set() works at edge (0,0)', function() { + test('set() works at edge (0,0)', function () { myp5.background(255); const gfx = myp5.createGraphics(30, 30); gfx.background(0, 0, 255); @@ -270,7 +293,7 @@ suite('Rendering', function() { assert.deepEqual(myp5.get(35, 35), [255, 255, 255, 255]); }); - test('set() behaves same for p5.Graphics and p5.Image', function() { + test('set() behaves same for p5.Graphics and p5.Image', function () { const gfx = myp5.createGraphics(20, 20); gfx.background(255, 0, 0); const img = gfx.get(); @@ -286,39 +309,38 @@ suite('Rendering', function() { }); }); - suite('p5.prototype.createGraphics pixelDensity', function() { + suite('p5.prototype.createGraphics pixelDensity', function () { let myp5; - beforeEach(function() { - myp5 = new p5(function(p) { - p.setup = function() { + beforeEach(function () { + myp5 = new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100); }; }); }); - afterEach(function() { + afterEach(function () { myp5.remove(); }); - test('createGraphics should inherit pixelDensity from parent sketch', function() { + test('createGraphics should inherit pixelDensity from parent sketch', function () { myp5.pixelDensity(1); const g = myp5.createGraphics(100, 100); assert.equal(g.pixelDensity(), 1); }); - test('createGraphics should inherit non-default pixelDensity', function() { + test('createGraphics should inherit non-default pixelDensity', function () { myp5.pixelDensity(3); const g = myp5.createGraphics(100, 100); assert.equal(g.pixelDensity(), 3); }); - test('createGraphics should use default pixelDensity when not explicitly set', function() { + test('createGraphics should use default pixelDensity when not explicitly set', function () { // When no pixelDensity is set, both should match const expectedDensity = myp5.pixelDensity(); const g = myp5.createGraphics(100, 100); assert.equal(g.pixelDensity(), expectedDensity); }); }); - }); diff --git a/test/unit/core/sketch_overrides.js b/test/unit/core/sketch_overrides.js index 3504c00e32..44a045f6a1 100644 --- a/test/unit/core/sketch_overrides.js +++ b/test/unit/core/sketch_overrides.js @@ -1,15 +1,13 @@ -import { - verifierUtils -} from '../../../src/friendly_errors/sketch_verifier.js'; +import { verifierUtils } from '../../../src/friendly_errors/sketch_verifier.js'; suite('Sketch Verifier', function () { const mockP5 = { _validateParameters: vi.fn(), - Color: function () { }, - Vector: function () { }, + Color: function () {}, + Vector: function () {}, prototype: { - rect: function () { }, - ellipse: function () { } + rect: function () {}, + ellipse: function () {} } }; @@ -55,13 +53,13 @@ suite('Sketch Verifier', function () { `; - vi.spyOn(document, 'querySelectorAll') - .mockImplementation((...args) => - fakeDocument.querySelectorAll(...args) - ); + vi.spyOn(document, 'querySelectorAll').mockImplementation((...args) => + fakeDocument.querySelectorAll(...args) + ); - vi.spyOn(verifierUtils, 'fetchScript') - .mockImplementation(() => Promise.resolve(userCode)); + vi.spyOn(verifierUtils, 'fetchScript').mockImplementation(() => + Promise.resolve(userCode) + ); const result = await verifierUtils.getUserCode(); @@ -84,44 +82,44 @@ suite('Sketch Verifier', function () { const result = verifierUtils.extractUserDefinedVariablesAndFuncs(code); const expectedResult = { - 'functions': [ + functions: [ { - 'line': 5, - 'name': 'foo' + line: 5, + name: 'foo' }, { - 'line': 6, - 'name': 'bar' + line: 6, + name: 'bar' }, { - 'line': 7, - 'name': 'baz' + line: 7, + name: 'baz' } ], - 'variables': [ + variables: [ { - 'line': 1, - 'name': 'x' + line: 1, + name: 'x' }, { - 'line': 2, - 'name': 'y' + line: 2, + name: 'y' }, { - 'line': 3, - 'name': 'z' + line: 3, + name: 'z' }, { - 'line': 4, - 'name': 'v1' + line: 4, + name: 'v1' }, { - 'line': 4, - 'name': 'v2' + line: 4, + name: 'v2' }, { - 'line': 4, - 'name': 'v3' + line: 4, + name: 'v3' } ] }; @@ -151,23 +149,23 @@ suite('Sketch Verifier', function () { const result = verifierUtils.extractUserDefinedVariablesAndFuncs(code); const expectedResult = { - 'functions': [], - 'variables': [ + functions: [], + variables: [ { - 'line': 2, - 'name': 'x' + line: 2, + name: 'x' }, { - 'line': 6, - 'name': 'y' + line: 6, + name: 'y' }, { - 'line': 11, - 'name': 'z' + line: 11, + name: 'z' }, { - 'line': 13, - 'name': 'i' + line: 13, + name: 'i' } ] }; @@ -177,11 +175,12 @@ suite('Sketch Verifier', function () { test('Handles parsing errors', function () { const invalidCode = 'let x = ;'; - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { }); + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); - const result = verifierUtils.extractUserDefinedVariablesAndFuncs( - invalidCode - ); + const result = + verifierUtils.extractUserDefinedVariablesAndFuncs(invalidCode); expect(consoleSpy).toHaveBeenCalled(); expect(result).toEqual({ variables: [], functions: [] }); @@ -200,7 +199,7 @@ suite('Sketch Verifier', function () { } beforeEach(function () { - consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { }); + consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); }); afterEach(function () { diff --git a/test/unit/core/structure.js b/test/unit/core/structure.js index 8d32128002..9a36e71a13 100644 --- a/test/unit/core/structure.js +++ b/test/unit/core/structure.js @@ -1,22 +1,26 @@ import p5 from '../../../src/app.js'; -import { testSketchWithPromise, createP5Iframe, P5_SCRIPT_TAG } from '../../js/p5_helpers'; +import { + testSketchWithPromise, + createP5Iframe, + P5_SCRIPT_TAG +} from '../../js/p5_helpers'; -suite('Structure', function() { +suite('Structure', function () { var myp5; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('p5.prototype.isLooping', function() { + suite('p5.prototype.isLooping', function () { // This suite uses its own p5 instance rather than the shared myp5: // toggling noLoop()/loop() within the same frame would double-pump // the shared instance's draw loop (noLoop() doesn't cancel the @@ -24,39 +28,39 @@ suite('Structure', function() { // one), breaking the timing-based tests below. var loopSketch; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { loopSketch = p; }; }); }); - afterAll(function() { + afterAll(function () { loopSketch.remove(); }); - test('isLooping should return true by default', function() { + test('isLooping should return true by default', function () { assert.strictEqual(loopSketch.isLooping(), true); }); - test('isLooping should return false after noLoop()', function() { + test('isLooping should return false after noLoop()', function () { loopSketch.noLoop(); assert.strictEqual(loopSketch.isLooping(), false); }); - test('isLooping should return true after loop()', function() { + test('isLooping should return true after loop()', function () { loopSketch.loop(); assert.strictEqual(loopSketch.isLooping(), true); }); }); - suite('p5.prototype.loop and p5.prototype.noLoop', function() { - test('noLoop should stop', function() { - return new Promise(function(resolve, reject) { + suite('p5.prototype.loop and p5.prototype.noLoop', function () { + test('noLoop should stop', function () { + return new Promise(function (resolve, reject) { var c0 = myp5.frameCount; myp5.noLoop(); - myp5.draw = function() { + myp5.draw = function () { var c1 = myp5.frameCount; // Allow one final draw to run if (c1 > c0 + 1) { @@ -67,11 +71,11 @@ suite('Structure', function() { }); }); - test('loop should restart', function() { - return new Promise(function(resolve, reject) { + test('loop should restart', function () { + return new Promise(function (resolve, reject) { var c0 = myp5.frameCount; myp5.noLoop(); - myp5.draw = function() { + myp5.draw = function () { var c1 = myp5.frameCount; // Allow one final draw to run if (c1 > c0 + 1) { @@ -79,11 +83,11 @@ suite('Structure', function() { } }; setTimeout(resolve, 100); - }).then(function() { - return new Promise(function(resolve, reject) { + }).then(function () { + return new Promise(function (resolve, reject) { myp5.draw = resolve; myp5.loop(); - setTimeout(function() { + setTimeout(function () { reject('Failed to restart draw.'); }, 100); }); @@ -91,7 +95,7 @@ suite('Structure', function() { }); }); - suite('p5.prototype.push and p5.prototype.pop', function() { + suite('p5.prototype.push and p5.prototype.pop', function () { function getRenderState() { return { ...myp5._renderer.states }; } @@ -104,146 +108,146 @@ suite('Structure', function() { assert.deepEqual(getRenderState(), originalState); } - test('leak no state after fill()', function() { + test('leak no state after fill()', function () { myp5.noFill(); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.fill('red'); }); }); - test('leak no state after noFill()', function() { + test('leak no state after noFill()', function () { myp5.fill('red'); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.noFill(); }); }); - test('leak no state after stroke()', function() { + test('leak no state after stroke()', function () { myp5.noStroke(); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.stroke('red'); }); }); - test('leak no state after noStroke()', function() { + test('leak no state after noStroke()', function () { myp5.stroke('red'); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.noStroke(); }); }); - test('leak no state after tint()', function() { + test('leak no state after tint()', function () { myp5.noTint(); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.tint(255, 0, 0); }); }); - test('leak no state after noTint()', function() { + test('leak no state after noTint()', function () { myp5.tint(255, 0, 0); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.noTint(); }); }); - test('leak no state after strokeWeight()', function() { + test('leak no state after strokeWeight()', function () { myp5.strokeWeight(1); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.strokeWeight(10); }); }); - test('leak no state after strokeCap()', function() { + test('leak no state after strokeCap()', function () { myp5.strokeCap(myp5.ROUND); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.strokeCap(myp5.SQUARE); }); }); - test('leak no state after strokeJoin()', function() { + test('leak no state after strokeJoin()', function () { myp5.strokeJoin(myp5.BEVEL); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.strokeJoin(myp5.MITER); }); }); - test('leak no state after imageMode()', function() { + test('leak no state after imageMode()', function () { myp5.imageMode(myp5.CORNER); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.imageMode(myp5.CENTER); }); }); - test('leak no state after rectMode()', function() { + test('leak no state after rectMode()', function () { myp5.rectMode(myp5.CORNER); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.rectMode(myp5.CENTER); }); }); - test('leak no state after ellipseMode()', function() { + test('leak no state after ellipseMode()', function () { myp5.ellipseMode(myp5.CORNER); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.ellipseMode(myp5.CENTER); }); }); - test('leak no state after colorMode()', function() { + test('leak no state after colorMode()', function () { myp5.colorMode(myp5.HSB); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.colorMode(myp5.RGB); }); }); - test('leak no state after textAlign()', function() { + test('leak no state after textAlign()', function () { myp5.textAlign(myp5.RIGHT, myp5.BOTTOM); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.textAlign(myp5.CENTER, myp5.CENTER); }); }); - test('leak no state after textFont()', function() { + test('leak no state after textFont()', function () { myp5.textFont('Georgia'); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.textFont('Helvetica'); }); }); - test('leak no state after textStyle()', function() { + test('leak no state after textStyle()', function () { myp5.textStyle(myp5.ITALIC); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.textStyle(myp5.BOLD); }); }); - test('leak no state after textSize()', function() { + test('leak no state after textSize()', function () { myp5.textSize(12); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.textSize(16); }); }); - test('leak no state after textLeading()', function() { + test('leak no state after textLeading()', function () { myp5.textLeading(20); - assertCanPreserveRenderState(function() { + assertCanPreserveRenderState(function () { myp5.textLeading(30); }); }); }); - suite('p5.prototype.redraw', function() { + suite('p5.prototype.redraw', function () { var iframe; - afterAll(function() { + afterAll(function () { if (iframe) { iframe.teardown(); iframe = null; } }); - test('resets the rendering matrix between frames', function() { - return new Promise(function(resolve, reject) { - myp5.draw = function() { + test('resets the rendering matrix between frames', function () { + return new Promise(function (resolve, reject) { + myp5.draw = function () { myp5.background(0); myp5.stroke(255); myp5.point(10, 10); @@ -257,16 +261,16 @@ suite('Structure', function() { }); }); - test.todo('instance redraw is independent of window', function() { + test.todo('instance redraw is independent of window', function () { // callback for p5 instance mode. // It does not call noLoop so redraw will be called many times. // Redraw is not supposed to call window.draw even though no draw is defined in cb function cb(s) { - s.setup = function() { + s.setup = function () { setTimeout(window.afterSetup, 1000); }; } - return new Promise(function(resolve) { + return new Promise(function (resolve) { iframe = createP5Iframe( [ P5_SCRIPT_TAG, @@ -279,41 +283,39 @@ suite('Structure', function() { ].join('\n') ); iframe.elt.contentWindow.afterSetup = resolve; - }).then(function() { + }).then(function () { var win = iframe.elt.contentWindow; assert.strictEqual(win.globalDraws, 1); }); }); }); - suite('loop', function() { - testSketchWithPromise('loop in setup does not call draw', function( - sketch, - resolve, - reject - ) { - sketch.setup = function() { - sketch.loop(); - resolve(); - }; - - sketch.draw = function() { - reject(new Error('Entered draw during loop()')); - }; - }); + suite('loop', function () { + testSketchWithPromise( + 'loop in setup does not call draw', + function (sketch, resolve, reject) { + sketch.setup = function () { + sketch.loop(); + resolve(); + }; - testSketchWithPromise('loop in draw does not call draw', function( - sketch, - resolve, - reject - ) { - sketch.draw = function() { - if (sketch.frameCount > 1) { - reject(new Error('re-entered draw during loop() call')); - } - sketch.loop(); - resolve(); - }; - }); + sketch.draw = function () { + reject(new Error('Entered draw during loop()')); + }; + } + ); + + testSketchWithPromise( + 'loop in draw does not call draw', + function (sketch, resolve, reject) { + sketch.draw = function () { + if (sketch.frameCount > 1) { + reject(new Error('re-entered draw during loop() call')); + } + sketch.loop(); + resolve(); + }; + } + ); }); }); diff --git a/test/unit/core/transform.js b/test/unit/core/transform.js index c4b0edbb8e..e016893af3 100644 --- a/test/unit/core/transform.js +++ b/test/unit/core/transform.js @@ -1,77 +1,77 @@ import { mockP5, mockP5Prototype } from '../../js/mocks'; import transform from '../../../src/core/transform'; -suite('Transform', function() { - beforeAll(function() { +suite('Transform', function () { + beforeAll(function () { transform(mockP5, mockP5Prototype); }); - suite('p5.prototype.rotate', function() { - test('should be a function', function() { + suite('p5.prototype.rotate', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.rotate); assert.typeOf(mockP5Prototype.rotate, 'function'); }); }); - suite('p5.prototype.rotateX', function() { - test('should be a function', function() { + suite('p5.prototype.rotateX', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.rotateX); assert.typeOf(mockP5Prototype.rotateX, 'function'); }); - test('throws error. should be used in WEBGL mode', function() { - assert.throws(function() { + test('throws error. should be used in WEBGL mode', function () { + assert.throws(function () { mockP5Prototype.rotateX(100); }, Error); }); }); - suite('p5.prototype.rotateY', function() { - test('should be a function', function() { + suite('p5.prototype.rotateY', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.rotateY); assert.typeOf(mockP5Prototype.rotateY, 'function'); }); - test('throws error. should be used in WEBGL mode', function() { - assert.throws(function() { + test('throws error. should be used in WEBGL mode', function () { + assert.throws(function () { mockP5Prototype.rotateY(100); }, Error); }); }); - suite('p5.prototype.rotateZ', function() { - test('should be a function', function() { + suite('p5.prototype.rotateZ', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.rotateZ); assert.typeOf(mockP5Prototype.rotateZ, 'function'); }); - test('throws error. should be used in WEBGL mode', function() { - assert.throws(function() { + test('throws error. should be used in WEBGL mode', function () { + assert.throws(function () { mockP5Prototype.rotateZ(100); }, Error); }); }); - suite('p5.prototype.scale', function() { - test('should be a function', function() { + suite('p5.prototype.scale', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.scale); assert.typeOf(mockP5Prototype.scale, 'function'); }); }); - suite('p5.prototype.shearX', function() { - test('should be a function', function() { + suite('p5.prototype.shearX', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.shearX); assert.typeOf(mockP5Prototype.shearX, 'function'); }); }); - suite('p5.prototype.shearY', function() { - test('should be a function', function() { + suite('p5.prototype.shearY', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.shearY); assert.typeOf(mockP5Prototype.shearY, 'function'); }); }); - suite('p5.prototype.translate', function() { - test('should be a function', function() { + suite('p5.prototype.translate', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.translate); assert.typeOf(mockP5Prototype.translate, 'function'); }); diff --git a/test/unit/core/version.js b/test/unit/core/version.js index a69d33e654..0530a20bca 100644 --- a/test/unit/core/version.js +++ b/test/unit/core/version.js @@ -1,27 +1,27 @@ import p5 from '../../../src/app.js'; -suite('Version', function() { +suite('Version', function () { var myp5; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - test('exists on p5 object', function() { + test('exists on p5 object', function () { assert.isString(p5.VERSION); // ensure the string isn't empty assert.isTrue(p5.VERSION.length > 0); }); - test('exists on instance of p5 sketch', function() { + test('exists on instance of p5 sketch', function () { assert.isString(myp5.VERSION); // ensure the string isn't empty assert.isTrue(myp5.VERSION.length > 0); diff --git a/test/unit/core/vertex.js b/test/unit/core/vertex.js index 357d1e7ab6..6288d644de 100644 --- a/test/unit/core/vertex.js +++ b/test/unit/core/vertex.js @@ -1,52 +1,52 @@ import p5 from '../../../src/app.js'; import { vi } from 'vitest'; -suite('Vertex', function() { +suite('Vertex', function () { var myp5; - beforeEach(function() { - new p5(function(p) { - p.setup = function() { + beforeEach(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterEach(function() { + afterEach(function () { vi.restoreAllMocks(); myp5.remove(); }); - suite('p5.prototype.beginShape', function() { - test('should be a function', function() { + suite('p5.prototype.beginShape', function () { + test('should be a function', function () { assert.ok(myp5.beginShape); assert.typeOf(myp5.beginShape, 'function'); }); }); - suite('p5.prototype.bezierVertex', function() { - test('should be a function', function() { + suite('p5.prototype.bezierVertex', function () { + test('should be a function', function () { assert.ok(myp5.bezierVertex); assert.typeOf(myp5.bezierVertex, 'function'); }); }); - suite('p5.prototype.splineVertex', function() { - test('should be a function', function() { + suite('p5.prototype.splineVertex', function () { + test('should be a function', function () { assert.ok(myp5.splineVertex); assert.typeOf(myp5.splineVertex, 'function'); }); }); - suite('p5.prototype.endShape', function() { - test('should be a function', function() { + suite('p5.prototype.endShape', function () { + test('should be a function', function () { assert.ok(myp5.endShape); assert.typeOf(myp5.endShape, 'function'); }); }); - suite('p5.prototype.vertex', function() { - test('should be a function', function() { + suite('p5.prototype.vertex', function () { + test('should be a function', function () { assert.ok(myp5.vertex); assert.typeOf(myp5.vertex, 'function'); }); diff --git a/test/unit/data/local_storage.js b/test/unit/data/local_storage.js index 6c8719c777..1b7dc71fa6 100644 --- a/test/unit/data/local_storage.js +++ b/test/unit/data/local_storage.js @@ -5,7 +5,7 @@ import creatingReading from '../../../src/color/creating_reading'; import p5Vector from '../../../src/math/p5.Vector'; import math from '../../../src/math/math'; -suite('local storage', function() { +suite('local storage', function () { const myBoolean = false; const myObject = { one: 1, two: { nested: true } }; const myNumber = 46; @@ -15,7 +15,7 @@ suite('local storage', function() { const hardCodedTypeID = 'p5TypeID'; - beforeAll(function() { + beforeAll(function () { storage(mockP5, mockP5Prototype); p5Color(mockP5, mockP5Prototype, {}); creatingReading(mockP5, mockP5Prototype); @@ -33,78 +33,82 @@ suite('local storage', function() { mockP5Prototype.storeItem('myVector', myVector); }); - suite('all keys and type keys should exist in local storage', function() { - test('boolean storage retrieval should work', function() { + suite('all keys and type keys should exist in local storage', function () { + test('boolean storage retrieval should work', function () { assert.equal(mockP5Prototype.getItem('myBoolean'), false); }); - test('boolean storage should store the correct type ID', function() { + test('boolean storage should store the correct type ID', function () { assert.equal( - localStorage.getItem('myBoolean' + hardCodedTypeID), 'boolean' + localStorage.getItem('myBoolean' + hardCodedTypeID), + 'boolean' ); }); - test('object storage should work', function() { + test('object storage should work', function () { assert.deepEqual(mockP5Prototype.getItem('myObject'), { one: 1, two: { nested: true } }); }); - test('object storage retrieval should store the correct type ID', function() { + test('object storage retrieval should store the correct type ID', function () { assert.equal( - localStorage.getItem('myObject' + hardCodedTypeID), 'object' + localStorage.getItem('myObject' + hardCodedTypeID), + 'object' ); }); - test('number storage retrieval should work', function() { + test('number storage retrieval should work', function () { assert.equal(mockP5Prototype.getItem('myNumber'), 46); }); - test('number storage should store the correct type ID', function() { + test('number storage should store the correct type ID', function () { assert.equal( - localStorage.getItem('myNumber' + hardCodedTypeID), 'number' + localStorage.getItem('myNumber' + hardCodedTypeID), + 'number' ); }); - test('string storage retrieval should work', function() { + test('string storage retrieval should work', function () { assert.equal(mockP5Prototype.getItem('myString'), 'coolio'); }); - test('string storage should store the correct type ID', function() { + test('string storage should store the correct type ID', function () { assert.equal( - localStorage.getItem('myString' + hardCodedTypeID), 'string' + localStorage.getItem('myString' + hardCodedTypeID), + 'string' ); }); - test('p5 Color should retrieve as p5 Color', function() { + test('p5 Color should retrieve as p5 Color', function () { assert.instanceOf(mockP5Prototype.getItem('myColor'), mockP5.Color); }); - test('p5 Vector should retrieve as p5 Vector', function() { + test('p5 Vector should retrieve as p5 Vector', function () { assert.instanceOf(mockP5Prototype.getItem('myVector'), mockP5.Vector); }); }); - var checkRemoval = function(key) { + var checkRemoval = function (key) { mockP5Prototype.removeItem(key); assert.deepEqual(mockP5Prototype.getItem(key), null); assert.deepEqual(mockP5Prototype.getItem(key + hardCodedTypeID), null); }; - suite('should be able to remove all items', function() { - test('boolean should be removable', function() { + suite('should be able to remove all items', function () { + test('boolean should be removable', function () { checkRemoval('myBoolean'); }); - test('number should be removable', function() { + test('number should be removable', function () { checkRemoval('myNumber'); }); - test('object should be removable', function() { + test('object should be removable', function () { checkRemoval('myObject'); }); - test('string should be removable', function() { + test('string should be removable', function () { checkRemoval('myString'); }); - test('color should be removable', function() { + test('color should be removable', function () { checkRemoval('myColor'); }); - test('vector should be removable', function() { + test('vector should be removable', function () { checkRemoval('myVector'); }); }); diff --git a/test/unit/dom/dom.js b/test/unit/dom/dom.js index 2621ac19b7..4469096e84 100644 --- a/test/unit/dom/dom.js +++ b/test/unit/dom/dom.js @@ -6,7 +6,7 @@ import { Element } from '../../../src/dom/p5.Element'; import creatingReading from '../../../src/color/creating_reading'; import p5Color from '../../../src/color/p5.Color'; -suite('DOM', function() { +suite('DOM', function () { beforeAll(() => { dom(mockP5, mockP5Prototype); creatingReading(mockP5, mockP5Prototype); @@ -14,7 +14,7 @@ suite('DOM', function() { }); // Selectors - suite('p5.prototype.select', function() { + suite('p5.prototype.select', function () { let myp5Container; const generateButton = (name, className = null) => { @@ -36,16 +36,16 @@ suite('DOM', function() { return div; }; - beforeEach(function() { + beforeEach(function () { myp5Container = document.createElement('div'); document.body.appendChild(myp5Container); }); - afterEach(function() { + afterEach(function () { document.body.innerHTML = ''; }); - test('should return only one p5.element if match is found', function() { + test('should return only one p5.element if match is found', function () { // Adding 2 buttons to container mockP5Prototype.createCheckbox('Text 1'); mockP5Prototype.createCheckbox('Text 2'); @@ -54,7 +54,7 @@ suite('DOM', function() { assert.instanceOf(result, Element); }); - test('should find element by class name', function() { + test('should find element by class name', function () { // Creates 2 buttons with same class and test if it selects only one. const testClassName = 'test-button'; const testButton = generateButton('Button 1', testClassName); @@ -64,7 +64,7 @@ suite('DOM', function() { assert.deepEqual(result.elt, testButton.elt); }); - test('should find element by class name from given container', function() { + test('should find element by class name from given container', function () { // Creates 2 buttons with same class in a container and test if it selects only one. const testClassName = 'test-button'; generateButton('Button 1', testClassName); @@ -76,7 +76,7 @@ suite('DOM', function() { assert.deepEqual(testButton.elt, result.elt); }); - test('should return null when no matches are found by class name', function() { + test('should return null when no matches are found by class name', function () { // Gives unused className and tests if it returns null const testClassName = 'test-button'; generateButton('Test Button', testClassName); @@ -84,7 +84,7 @@ suite('DOM', function() { assert.isNull(result); }); - test('should find element by tag name', function() { + test('should find element by tag name', function () { // Creates 2 similar elements and tests if it selects only one. const testButton = generateButton('Button 1'); generateButton('Button 2'); @@ -92,7 +92,7 @@ suite('DOM', function() { assert.deepEqual(result.elt, testButton.elt); }); - test('should find element by tag name from given container', function() { + test('should find element by tag name from given container', function () { // Creates 2 elements inside and outside of a container and tests if it // selects inside the container generateButton('Button 1'); @@ -104,13 +104,13 @@ suite('DOM', function() { assert.deepEqual(result.elt, testButton.elt); }); - test('should return null when no matches are found by tag name', function() { + test('should return null when no matches are found by tag name', function () { generateButton('Test Button'); const result = mockP5Prototype.select('video', myp5Container); assert.isNull(result); }); - test('should select element in container using CSS selector with ID', function() { + test('should select element in container using CSS selector with ID', function () { const divID = 'divId'; const testDiv = generateDiv(divID); const testButton = generateButton('Button 1'); @@ -121,7 +121,7 @@ suite('DOM', function() { assert.deepEqual(result.elt, testButton.elt); }); - test('should select element in container using CSS selector with class name', function() { + test('should select element in container using CSS selector with class name', function () { const divClass = 'divClass'; const testDiv = generateDiv(null, divClass); const testButton = generateButton('Button 1'); @@ -133,8 +133,8 @@ suite('DOM', function() { }); }); - suite('p5.prototype.selectAll', function() { - beforeEach(function() { + suite('p5.prototype.selectAll', function () { + beforeEach(function () { const mydiv = document.createElement('div'); mydiv.setAttribute('id', 'main'); @@ -148,16 +148,16 @@ suite('DOM', function() { document.body.append(mydiv, otherbutton); }); - afterEach(function() { + afterEach(function () { document.body.innerHTML = ''; }); - test('should return an array', function() { + test('should return an array', function () { const elements = mockP5Prototype.selectAll('button'); assert.isArray(elements); }); - test('should return empty array when no matching classes are found', function() { + test('should return empty array when no matching classes are found', function () { const elements = mockP5Prototype.selectAll('.randomElements'); assert.isArray(elements); assert.lengthOf(elements, 0); @@ -170,14 +170,14 @@ suite('DOM', function() { } }; - test('should find all elements with matching class name', function() { + test('should find all elements with matching class name', function () { const testClassName = 'p5button'; const p5Results = mockP5Prototype.selectAll(`.${testClassName}`); const domResults = document.getElementsByClassName(testClassName); matchResults(p5Results, domResults); }); - test('should find all elements with matching class name in given container', function() { + test('should find all elements with matching class name in given container', function () { const testClassName = 'p5button'; const parentContainerId = 'main'; const p5Results = mockP5Prototype.selectAll( @@ -189,26 +189,31 @@ suite('DOM', function() { matchResults(p5Results, domResults); }); - test('should find all elements with matching tag name', function() { + test('should find all elements with matching tag name', function () { const testTagName = 'button'; const p5Results = mockP5Prototype.selectAll(testTagName); const domResults = document.getElementsByTagName(testTagName); matchResults(p5Results, domResults); }); - test('should find all elements with matching tag name in given container', function() { + test('should find all elements with matching tag name in given container', function () { const testTagName = 'button'; const parentContainerId = 'main'; - const p5Results = mockP5Prototype.selectAll(testTagName, `#${parentContainerId}`); + const p5Results = mockP5Prototype.selectAll( + testTagName, + `#${parentContainerId}` + ); const containerElement = document.getElementById(parentContainerId); const domResults = containerElement.getElementsByTagName(testTagName); matchResults(p5Results, domResults); }); - test('should find all elements in container using CSS selector with id', function() { + test('should find all elements in container using CSS selector with id', function () { const testTagName = 'button'; const parentContainerId = 'main'; - const p5Results = mockP5Prototype.selectAll(`#${parentContainerId} ${testTagName}`); + const p5Results = mockP5Prototype.selectAll( + `#${parentContainerId} ${testTagName}` + ); const containerElement = document.getElementById(parentContainerId); const domResults = containerElement.getElementsByTagName(testTagName); matchResults(p5Results, domResults); @@ -216,11 +221,11 @@ suite('DOM', function() { }); // Events - suite.todo('p5.Element.prototype.changed', function() { + suite.todo('p5.Element.prototype.changed', function () { testSketchWithPromise( 'should trigger callback when element changes', - function(sketch, resolve, reject) { - sketch.setup = function() { + function (sketch, resolve, reject) { + sketch.setup = function () { const testElement = sketch.createSlider(0, 100, 50, 10); testElement.changed(resolve); testElement.elt.dispatchEvent(new Event('change')); @@ -230,8 +235,8 @@ suite('DOM', function() { testSketchWithPromise( 'should not trigger callback after changed(false) is called', - function(sketch, resolve, reject) { - sketch.setup = function() { + function (sketch, resolve, reject) { + sketch.setup = function () { const testElement = sketch.createSlider(0, 100, 50, 10); // if callback is called, there is some error. so reject testElement.changed(reject); @@ -243,11 +248,11 @@ suite('DOM', function() { ); }); - suite.todo('p5.Element.prototype.input', function() { + suite.todo('p5.Element.prototype.input', function () { testSketchWithPromise( 'should trigger callback when input is provided', - function(sketch, resolve, reject) { - sketch.setup = function() { + function (sketch, resolve, reject) { + sketch.setup = function () { const testElement = sketch.createElement('input'); testElement.input(resolve); testElement.elt.dispatchEvent(new Event('input')); @@ -257,8 +262,8 @@ suite('DOM', function() { testSketchWithPromise( 'should not trigger callback after input(false) is called', - function(sketch, resolve, reject) { - sketch.setup = function() { + function (sketch, resolve, reject) { + sketch.setup = function () { const testElement = sketch.createElement('input'); // if callback is called, there is some error. so reject testElement.input(reject); @@ -270,138 +275,137 @@ suite('DOM', function() { ); }); - suite.todo('p5.prototype.drop', function() { - testSketchWithPromise('drop fires multiple events', function( - sketch, - resolve, - reject - ) { - let testElement; - let fileFnCounter = 0; - let eventFnCounter = 0; - sketch.setup = function() { - testElement = sketch.createDiv('Drop files inside'); - - // Setup test functions and constants - const file1 = new File(['foo'], 'foo.txt', { type: 'text/plain' }); - const file2 = new File(['foo'], 'foo.txt', { type: 'text/plain' }); - const hasFinished = () => { - if (fileFnCounter > 1 && eventFnCounter === 1) resolve(); - }; - const testFileFn = () => { - fileFnCounter += 1; - hasFinished(); - }; - const testEventFn = () => { - eventFnCounter += 1; - hasFinished(); + suite.todo('p5.prototype.drop', function () { + testSketchWithPromise( + 'drop fires multiple events', + function (sketch, resolve, reject) { + let testElement; + let fileFnCounter = 0; + let eventFnCounter = 0; + sketch.setup = function () { + testElement = sketch.createDiv('Drop files inside'); + + // Setup test functions and constants + const file1 = new File(['foo'], 'foo.txt', { type: 'text/plain' }); + const file2 = new File(['foo'], 'foo.txt', { type: 'text/plain' }); + const hasFinished = () => { + if (fileFnCounter > 1 && eventFnCounter === 1) resolve(); + }; + const testFileFn = () => { + fileFnCounter += 1; + hasFinished(); + }; + const testEventFn = () => { + eventFnCounter += 1; + hasFinished(); + }; + testElement.drop(testFileFn, testEventFn); + + // Fire a mock drop and test the method + const mockedEvent = new Event('drop'); + mockedEvent.dataTransfer = { files: [file1, file2] }; + testElement.elt.dispatchEvent(mockedEvent); }; - testElement.drop(testFileFn, testEventFn); - - // Fire a mock drop and test the method - const mockedEvent = new Event('drop'); - mockedEvent.dataTransfer = { files: [file1, file2] }; - testElement.elt.dispatchEvent(mockedEvent); - }; - }); + } + ); }); // Add/remove elements - suite('p5.prototype.createDiv', function() { - afterEach(function() { + suite('p5.prototype.createDiv', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createDiv); }); - test('should return a p5.Element of div type', function() { + test('should return a p5.Element of div type', function () { const testElement = mockP5Prototype.createDiv(); assert.instanceOf(testElement, Element); assert.instanceOf(testElement.elt, HTMLDivElement); }); - test('should set given param as innerHTML of div', function() { + test('should set given param as innerHTML of div', function () { const testHTML = '

    Hello

    '; const testElement = mockP5Prototype.createDiv(testHTML); assert.deepEqual(testElement.elt.innerHTML, testHTML); }); }); - suite('p5.prototype.createP', function() { - afterEach(function() { + suite('p5.prototype.createP', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createP); }); - test('should return a p5.Element of p type', function() { + test('should return a p5.Element of p type', function () { const testElement = mockP5Prototype.createP(); assert.instanceOf(testElement, Element); assert.instanceOf(testElement.elt, HTMLParagraphElement); }); - test('should set given param as innerHTML of p', function() { + test('should set given param as innerHTML of p', function () { const testHTML = 'Hello'; const testElement = mockP5Prototype.createP(testHTML); assert.deepEqual(testElement.elt.innerHTML, testHTML); }); }); - suite('p5.prototype.createSpan', function() { - afterEach(function() { + suite('p5.prototype.createSpan', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createSpan); }); - test('should return a p5.Element of span type', function() { + test('should return a p5.Element of span type', function () { const testElement = mockP5Prototype.createSpan(); assert.instanceOf(testElement, Element); assert.instanceOf(testElement.elt, HTMLSpanElement); }); - test('should set given param as innerHTML of span', function() { + test('should set given param as innerHTML of span', function () { const testHTML = 'Hello'; const testElement = mockP5Prototype.createSpan(testHTML); assert.deepEqual(testElement.elt.innerHTML, testHTML); }); }); - suite('p5.prototype.createImg', function() { + suite('p5.prototype.createImg', function () { const imagePath = '/test/unit/assets/cat.jpg'; - afterEach(function() { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createImg); }); - test('should return p5.Element of image type', function() { + test('should return p5.Element of image type', function () { const testElement = mockP5Prototype.createImg(imagePath, ''); assert.instanceOf(testElement, Element); assert.instanceOf(testElement.elt, HTMLImageElement); }); - test('should set src of image from params', function() { + test('should set src of image from params', function () { const testElement = mockP5Prototype.createImg(imagePath, ''); assert.isTrue(testElement.elt.src.endsWith(imagePath)); }); - test('should set alt from params if given', function() { + test('should set alt from params if given', function () { const alternativeText = 'Picture of a cat'; const testElement = mockP5Prototype.createImg(imagePath, alternativeText); assert.deepEqual(testElement.elt.alt, alternativeText); }); - test('should set crossOrigin from params if given', function() { + test('should set crossOrigin from params if given', function () { const crossOrigin = 'anonymous'; const testElement = mockP5Prototype.createImg(imagePath, '', crossOrigin); assert.deepEqual(testElement.elt.crossOrigin, crossOrigin); @@ -418,8 +422,8 @@ suite('DOM', function() { // ); }); - suite('p5.prototype.createA', function() { - afterEach(function() { + suite('p5.prototype.createA', function () { + afterEach(function () { document.body.innerHTML = ''; }); @@ -429,7 +433,7 @@ suite('DOM', function() { assert.instanceOf(testElement.elt, HTMLAnchorElement); }); - test('creates anchor with given link & text', function() { + test('creates anchor with given link & text', function () { const testUrl = 'https://p5js.org/'; const testText = 'p5js website'; const testElement = mockP5Prototype.createA(testUrl, testText); @@ -438,15 +442,19 @@ suite('DOM', function() { assert.deepEqual(testElement.elt.text, testText); }); - test('creates anchor with given target', function() { + test('creates anchor with given target', function () { const testTarget = 'blank'; - const testElement = mockP5Prototype.createA('https://p5js.org', 'p5js website', testTarget); + const testElement = mockP5Prototype.createA( + 'https://p5js.org', + 'p5js website', + testTarget + ); assert.deepEqual(testElement.elt.target, testTarget); }); }); - suite('p5.prototype.createSlider', function() { - afterEach(function() { + suite('p5.prototype.createSlider', function () { + afterEach(function () { document.body.innerHTML = ''; }); @@ -457,30 +465,33 @@ suite('DOM', function() { assert.deepEqual(testElement.elt.type, 'range'); }); - test('should set min and max values', function() { + test('should set min and max values', function () { const testElement = mockP5Prototype.createSlider(20, 80); assert.deepEqual(testElement.elt.min, '20'); assert.deepEqual(testElement.elt.max, '80'); }); - test('should set slider position', function() { + test('should set slider position', function () { const testElement = mockP5Prototype.createSlider(20, 80, 50); assert.deepEqual(testElement.elt.value, '50'); }); - test('should set step value', function() { + test('should set step value', function () { const testElement = mockP5Prototype.createSlider(20, 80, 10, 5); assert.deepEqual(testElement.elt.step, '5'); }); }); - suite('p5.prototype.createButton', function() { - afterEach(function() { + suite('p5.prototype.createButton', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should return a p5.Element of button type', function() { - const testElement = mockP5Prototype.createButton('testButton', 'testButton'); + test('should return a p5.Element of button type', function () { + const testElement = mockP5Prototype.createButton( + 'testButton', + 'testButton' + ); assert.instanceOf(testElement, Element); assert.instanceOf(testElement.elt, HTMLButtonElement); }); @@ -497,8 +508,8 @@ suite('DOM', function() { // ); }); - suite('p5.prototype.createCheckbox', function() { - afterEach(function() { + suite('p5.prototype.createCheckbox', function () { + afterEach(function () { document.body.innerHTML = ''; }); @@ -513,11 +524,11 @@ suite('DOM', function() { ? el.elt.firstElementChild.getElementsByTagName('input')[0] : null; - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createCheckbox); }); - test('should return a p5.Element with checkbox as descendant', function() { + test('should return a p5.Element with checkbox as descendant', function () { const testElement = mockP5Prototype.createCheckbox(); const checkboxElement = getCheckboxElement(testElement); @@ -525,7 +536,7 @@ suite('DOM', function() { assert.instanceOf(checkboxElement, HTMLInputElement); }); - test('calling createCheckbox(label) should create checkbox and set its label', function() { + test('calling createCheckbox(label) should create checkbox and set its label', function () { const labelValue = 'label'; const testElement = mockP5Prototype.createCheckbox(labelValue); const spanElement = getSpanElement(testElement); @@ -538,7 +549,7 @@ suite('DOM', function() { assert.deepEqual(testElementLabelValue, labelValue); }); - test('calling createCheckbox(label, true) should create a checked checkbox and set its label', function() { + test('calling createCheckbox(label, true) should create a checked checkbox and set its label', function () { const labelValue = 'label'; const testElement = mockP5Prototype.createCheckbox(labelValue, true); @@ -553,20 +564,20 @@ suite('DOM', function() { assert.isTrue(testElement.checked()); }); - test('calling checked() should return checked value of checkbox', function() { + test('calling checked() should return checked value of checkbox', function () { const testElement = mockP5Prototype.createCheckbox('', true); assert.isTrue(testElement.checked()); }); - test('calling checked(true) should set checked value of checkbox', function() { + test('calling checked(true) should set checked value of checkbox', function () { const testElement = mockP5Prototype.createCheckbox('', false); testElement.checked(true); assert.isTrue(testElement.checked()); }); }); - suite('p5.prototype.createSelect', function() { - afterEach(function() { + suite('p5.prototype.createSelect', function () { + afterEach(function () { document.body.innerHTML = ''; }); @@ -581,23 +592,23 @@ suite('DOM', function() { return selectElement; }; - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createSelect); }); - test('should return p5.Element of select HTML Element', function() { + test('should return p5.Element of select HTML Element', function () { const testElement = mockP5Prototype.createSelect(); assert.instanceOf(testElement, Element); assert.instanceOf(testElement.elt, HTMLSelectElement); }); - test('should return p5.Element when select element is passed', function() { + test('should return p5.Element when select element is passed', function () { const selectElement = createHTMLSelect(['option1', 'option2']); const testElement = mockP5Prototype.createSelect(selectElement); assert.deepEqual(testElement.elt, selectElement); }); - test('calling option(newName) should add a new option', function() { + test('calling option(newName) should add a new option', function () { const testOptions = ['John', 'Bethany', 'Dwayne']; const testElement = mockP5Prototype.createSelect(); for (const optionName of testOptions) testElement.option(optionName); @@ -610,7 +621,7 @@ suite('DOM', function() { assert.deepEqual(htmlOptions, testOptions); }); - test('calling option(name, newValue) should update value of option', function() { + test('calling option(name, newValue) should update value of option', function () { const optionName = 'john'; const testValues = [1, 'abc', true]; const testElement = mockP5Prototype.createSelect(); @@ -623,7 +634,7 @@ suite('DOM', function() { } }); - test('calling value() should return current selected option', function() { + test('calling value() should return current selected option', function () { const testElement = mockP5Prototype.createSelect(); testElement.option('Sunday'); testElement.option('Monday'); @@ -631,7 +642,7 @@ suite('DOM', function() { assert(testElement.value(), 'Monday'); }); - test('calling selected() should return all selected options', function() { + test('calling selected() should return all selected options', function () { const testElement = mockP5Prototype.createSelect(true); const options = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; for (const optionName of options) testElement.option(optionName); @@ -647,7 +658,7 @@ suite('DOM', function() { } }); - test('should update select value when HTML special characters are in the name', function() { + test('should update select value when HTML special characters are in the name', function () { const testElement = mockP5Prototype.createSelect(true); testElement.option('&', 'foo'); assert.equal(testElement.elt.options.length, 1); @@ -656,7 +667,7 @@ suite('DOM', function() { assert.equal(testElement.elt.options[0].value, 'bar'); }); - test('calling selected(value) should updated selectedIndex', function() { + test('calling selected(value) should updated selectedIndex', function () { const testElement = mockP5Prototype.createSelect(true); const options = ['Sunday', 'Monday', 'Tuesday', 'Friday']; for (const optionName of options) testElement.option(optionName); @@ -670,14 +681,14 @@ suite('DOM', function() { } }); - test('calling disable() should disable the whole dropdown', function() { + test('calling disable() should disable the whole dropdown', function () { const testElement = mockP5Prototype.createSelect(true); testElement.disable(); assert.isTrue(testElement.elt.disabled); }); - test('should disable an option when disable() method invoked with option name', function() { + test('should disable an option when disable() method invoked with option name', function () { const testElement = mockP5Prototype.createSelect(true); const options = ['Sunday', 'Monday', 'Tuesday', 'Friday']; for (const optionName of options) testElement.option(optionName); @@ -689,8 +700,8 @@ suite('DOM', function() { }); }); - suite('p5.prototype.createRadio', function() { - afterEach(function() { + suite('p5.prototype.createRadio', function () { + afterEach(function () { document.body.innerHTML = ''; }); @@ -719,17 +730,17 @@ suite('DOM', function() { ) .map(el => (isRadioInput(el) ? el : el.firstElementChild)); - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createRadio); }); - test('should return p5.Element of radio type', function() { + test('should return p5.Element of radio type', function () { const testElement = mockP5Prototype.createRadio(); assert.instanceOf(testElement, Element); assert.instanceOf(testElement.elt, HTMLDivElement); }); - test('should return p5.Element from existing radio Element', function() { + test('should return p5.Element from existing radio Element', function () { const options = ['Saturday', 'Sunday']; const radioElement = createRadioElement(options); const testElement = mockP5Prototype.createRadio(radioElement); @@ -737,7 +748,7 @@ suite('DOM', function() { assert.deepEqual(testElement.elt, radioElement); }); - test('calling option(value) should return existing radio element', function() { + test('calling option(value) should return existing radio element', function () { const options = ['Saturday', 'Sunday']; const radioElement = createRadioElement(options); const testElement = mockP5Prototype.createRadio(radioElement); @@ -748,7 +759,7 @@ suite('DOM', function() { assert.deepEqual(getChildren(testElement.elt).length, options.length); }); - test('calling option(newValue) should create a new radio input', function() { + test('calling option(newValue) should create a new radio input', function () { const testName = 'defaultRadio'; const options = ['Saturday', 'Sunday']; const testElement = mockP5Prototype.createRadio(testName); @@ -766,7 +777,7 @@ suite('DOM', function() { } }); - test('calling option(value, label) should set label of option', function() { + test('calling option(value, label) should set label of option', function () { const testName = 'defaultRadio'; const options = ['Saturday', 'Sunday']; const testElement = mockP5Prototype.createRadio(testName); @@ -780,7 +791,7 @@ suite('DOM', function() { } }); - test('should use given name for all options', function() { + test('should use given name for all options', function () { const testName = 'defaultRadio'; const options = ['Saturday', 'Sunday']; const radioElement = createRadioElement(options); @@ -792,7 +803,7 @@ suite('DOM', function() { } }); - test('calling remove(value) should remove option', function() { + test('calling remove(value) should remove option', function () { const options = ['Monday', 'Friday', 'Saturday', 'Sunday']; const radioElement = createRadioElement(options); const testElement = mockP5Prototype.createRadio(radioElement); @@ -808,7 +819,7 @@ suite('DOM', function() { assert.deepEqual(options, remainingOptions); }); - test('calling value() should return selected value', function() { + test('calling value() should return selected value', function () { const options = ['Monday', 'Friday', 'Saturday', 'Sunday']; const selectedValue = options[1]; const testElement = mockP5Prototype.createRadio(); @@ -817,7 +828,7 @@ suite('DOM', function() { assert.deepEqual(testElement.value(), selectedValue); }); - test('calling selected(value) should select a value and return it', function() { + test('calling selected(value) should select a value and return it', function () { const options = ['Monday', 'Friday', 'Saturday', 'Sunday']; const testElement = mockP5Prototype.createRadio(); for (const option of options) testElement.option(option); @@ -829,7 +840,7 @@ suite('DOM', function() { } }); - test('calling selected() should return the currently selected option', function() { + test('calling selected() should return the currently selected option', function () { const options = ['Monday', 'Friday', 'Saturday', 'Sunday']; const testElement = mockP5Prototype.createRadio(); for (const option of options) testElement.option(option); @@ -841,7 +852,7 @@ suite('DOM', function() { assert.isTrue(selectedOption.checked); }); - test('calling disable() should disable all the radio inputs', function() { + test('calling disable() should disable all the radio inputs', function () { const options = ['Monday', 'Friday', 'Saturday', 'Sunday']; const testElement = mockP5Prototype.createRadio(); for (const option of options) testElement.option(option); @@ -853,16 +864,16 @@ suite('DOM', function() { }); }); - suite('p5.prototype.createColorPicker', function() { - afterEach(function() { + suite('p5.prototype.createColorPicker', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createColorPicker); }); - test('should return p5.Element of input[color] type', function() { + test('should return p5.Element of input[color] type', function () { const testElement = mockP5Prototype.createColorPicker(); assert.instanceOf(testElement, Element); @@ -871,49 +882,49 @@ suite('DOM', function() { }); // TODO: pending finalization of p5.Color implementation - test.todo('should accept a p5.Color as param', function() { + test.todo('should accept a p5.Color as param', function () { const testColor = mockP5Prototype.color('red'); const testElement = mockP5Prototype.createColorPicker(testColor); assert.deepEqual(testElement.elt.value, testColor.toString('#rrggbb')); }); - test.todo('should accept a string as param', function() { + test.todo('should accept a string as param', function () { const testColorString = '#f00f00'; const testElement = mockP5Prototype.createColorPicker(testColorString); assert.deepEqual(testElement.elt.value, testColorString); }); - test.todo('calling color() should return the current color as p5.color', function() { + test.todo('calling color() should return the current color as p5.color', function () { const testColorString = 'red'; const testColor = mockP5Prototype.color(testColorString); const testElement = mockP5Prototype.createColorPicker(testColorString); assert.deepEqual(testElement.color(), testColor); }); - test.todo('calling value() should return hex string of color', function() { + test.todo('calling value() should return hex string of color', function () { const testColor = mockP5Prototype.color('aqua'); const testElement = mockP5Prototype.createColorPicker(testColor); assert.deepEqual(testElement.value(), testColor.toString('#rrggbb')); }); }); - suite('p5.prototype.createInput', function() { - afterEach(function() { + suite('p5.prototype.createInput', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createInput); }); - test('should return p5.Element of input type', function() { + test('should return p5.Element of input type', function () { const testElement = mockP5Prototype.createInput(); assert.instanceOf(testElement, Element); assert.instanceOf(testElement.elt, HTMLInputElement); }); - test('should set given value as input', function() { + test('should set given value as input', function () { const testValues = ['123', '', 'Hello world']; for (const value of testValues) { const testElement = mockP5Prototype.createInput(value); @@ -921,7 +932,7 @@ suite('DOM', function() { } }); - test('should create input of given type and value', function() { + test('should create input of given type and value', function () { const testType = 'password'; const testValue = '1234056789'; const testElement = mockP5Prototype.createInput(testValue, testType); @@ -930,8 +941,8 @@ suite('DOM', function() { }); }); - suite('p5.prototype.createFileInput', function() { - afterEach(function() { + suite('p5.prototype.createFileInput', function () { + afterEach(function () { document.body.innerHTML = ''; }); @@ -942,11 +953,11 @@ suite('DOM', function() { }); }; - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createFileInput); }); - test('should return input of file input', function() { + test('should return input of file input', function () { const testElement = mockP5Prototype.createFileInput(emptyCallback); assert.instanceOf(testElement, Element); assert.instanceOf(testElement.elt, HTMLInputElement); @@ -970,7 +981,7 @@ suite('DOM', function() { // } // ); - test('should accept multiple files if specified', function() { + test('should accept multiple files if specified', function () { const testElement = mockP5Prototype.createFileInput(emptyCallback, true); assert.isTrue(testElement.elt.multiple); }); @@ -1001,8 +1012,8 @@ suite('DOM', function() { // ); }); - suite('p5.prototype.createElement', function() { - afterEach(function() { + suite('p5.prototype.createElement', function () { + afterEach(function () { document.body.innerHTML = ''; }); @@ -1014,11 +1025,11 @@ suite('DOM', function() { video: HTMLVideoElement }; - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createElement); }); - test('should return a p5.Element of appropriate type', function() { + test('should return a p5.Element of appropriate type', function () { for (const [tag, domElName] of Object.entries(testData)) { const testElement = mockP5Prototype.createElement(tag); assert.instanceOf(testElement, Element); @@ -1026,19 +1037,19 @@ suite('DOM', function() { } }); - test('should set given content as innerHTML', function() { + test('should set given content as innerHTML', function () { const testContent = 'Lorem ipsum'; const testElement = mockP5Prototype.createElement('div', testContent); assert.deepEqual(testElement.elt.innerHTML, testContent); }); }); - suite('p5.prototype.removeElements', function() { - afterEach(function() { + suite('p5.prototype.removeElements', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should remove all elements created by p5 except Canvas', function() { + test('should remove all elements created by p5 except Canvas', function () { // creates 6 elements one of which is a canvas, then calls // removeElements and tests if only canvas is left. const tags = ['a', 'button', 'canvas', 'div', 'p', 'video']; @@ -1057,25 +1068,25 @@ suite('DOM', function() { }); // p5.Element.prototype.addClass - suite('p5.Element.prototype.addClass', function() { - afterEach(function() { + suite('p5.Element.prototype.addClass', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { // Create any p5.Element const testElement = mockP5Prototype.createElement('div'); assert.isFunction(testElement.addClass); }); - test('should add provided string to class names', function() { + test('should add provided string to class names', function () { const testClassName = 'jumbotron'; const testElement = mockP5Prototype.createElement('div'); testElement.addClass(testClassName); assert.deepEqual(testElement.elt.className, testClassName); }); - test('should not add class name, if already exists', function() { + test('should not add class name, if already exists', function () { const testClassName1 = 'jumbotron'; const testClassName2 = 'container-fluid'; const expectedClassName = testClassName1 + ' ' + testClassName2; @@ -1091,18 +1102,18 @@ suite('DOM', function() { }); // p5.Element.prototype.removeClass - suite('p5.Element.prototype.removeClass', function() { - afterEach(function() { + suite('p5.Element.prototype.removeClass', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { // Create any p5.Element const testElement = mockP5Prototype.createElement('div'); assert.isFunction(testElement.removeClass); }); - test('should remove provided string from class names', function() { + test('should remove provided string from class names', function () { const defaultClassNames = 'col-md-9 col-sm-12'; const testClassName = 'jumbotron'; @@ -1115,7 +1126,7 @@ suite('DOM', function() { assert.deepEqual(testElement.elt.className, defaultClassNames); }); - test('should not throw error if class name not exists', function() { + test('should not throw error if class name not exists', function () { const testClassName1 = 'jumbotron'; const testClassName2 = 'container-fluid'; @@ -1130,18 +1141,18 @@ suite('DOM', function() { }); // p5.Element.prototype.hasClass - suite('p5.Element.prototype.hasClass', function() { - afterEach(function() { + suite('p5.Element.prototype.hasClass', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { // Create any p5.Element const testElement = mockP5Prototype.createElement('div'); assert.isFunction(testElement.hasClass); }); - test('should return true for existing class name', function() { + test('should return true for existing class name', function () { const defaultClassNames = 'col-md-9 jumbotron'; const testClassName = 'jumbotron'; @@ -1151,7 +1162,7 @@ suite('DOM', function() { assert.isTrue(testElement.hasClass(testClassName)); }); - test('should return false for non-existing class name', function() { + test('should return false for non-existing class name', function () { const defaultClassNames = 'col-md-9 jumbotron'; const testClassName = 'container-fluid'; @@ -1163,18 +1174,18 @@ suite('DOM', function() { }); // p5.Element.prototype.toggleClass - suite('p5.Element.prototype.toggleClass', function() { - afterEach(function() { + suite('p5.Element.prototype.toggleClass', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { // Create any p5.Element const testElement = mockP5Prototype.createElement('div'); assert.isFunction(testElement.toggleClass); }); - test('should remove an existing class name', function() { + test('should remove an existing class name', function () { const defaultClassName = 'container-fluid'; const testClassName = 'jumbotron'; @@ -1186,7 +1197,7 @@ suite('DOM', function() { assert.deepEqual(testElement.elt.className, defaultClassName); }); - test('should add an non-existing class name', function() { + test('should add an non-existing class name', function () { const defaultClassName = 'container-fluid'; const testClassName = 'jumbotron'; @@ -1202,17 +1213,17 @@ suite('DOM', function() { }); // p5.Element.prototype.child - suite('p5.Element.prototype.child', function() { - afterEach(function() { + suite('p5.Element.prototype.child', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { const testElement = mockP5Prototype.createElement('div'); assert.isFunction(testElement.child); }); - test('should return all child nodes by default', function() { + test('should return all child nodes by default', function () { const testElement = mockP5Prototype.createElement('div'); const childElement = mockP5Prototype.createElement('p'); @@ -1227,7 +1238,7 @@ suite('DOM', function() { }); }); - test('should append p5 element as child', function() { + test('should append p5 element as child', function () { const testElement = mockP5Prototype.createElement('div'); const childElement = mockP5Prototype.createElement('p'); @@ -1236,7 +1247,7 @@ suite('DOM', function() { assert.isTrue(childNodes.includes(childElement.elt)); }); - test('should append dom element as child', function() { + test('should append dom element as child', function () { const testElement = mockP5Prototype.createElement('div'); const childElement = mockP5Prototype.createElement('p'); @@ -1245,7 +1256,7 @@ suite('DOM', function() { assert.isTrue(childNodes.includes(childElement.elt)); }); - test('should append element as child from a given id', function() { + test('should append element as child from a given id', function () { const testElement = mockP5Prototype.createElement('div'); const childId = 'testChildElement'; const childElement = mockP5Prototype.createElement('p'); @@ -1256,7 +1267,7 @@ suite('DOM', function() { assert.isTrue(childNodes.includes(childElement.elt)); }); - test('should not throw error if mathcing element is not found from a given id', function() { + test('should not throw error if mathcing element is not found from a given id', function () { const testElement = mockP5Prototype.createElement('div'); const randomChildId = 'testChildElement'; expect(() => testElement.child(randomChildId)).to.not.throw(); @@ -1264,12 +1275,12 @@ suite('DOM', function() { }); // p5.Element.prototype.center - suite('p5.Element.prototype.center', function() { - afterEach(function() { + suite('p5.Element.prototype.center', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { const testElement = mockP5Prototype.createElement('div'); assert.isFunction(testElement.center); }); @@ -1288,18 +1299,18 @@ suite('DOM', function() { }); // p5.Element.prototype.html - suite('p5.Element.prototype.html', function() { - afterEach(function() { + suite('p5.Element.prototype.html', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { // Create any p5.Element const testElement = mockP5Prototype.createElement('a'); assert.isFunction(testElement.position); }); - test('should return the inner HTML of element if no argument is given', function() { + test('should return the inner HTML of element if no argument is given', function () { const testElement = mockP5Prototype.createElement('div'); const testHTML = '

    Hello World

    '; @@ -1307,7 +1318,7 @@ suite('DOM', function() { assert.deepEqual(testElement.html(), testHTML); }); - test('should replace the inner HTML of element', function() { + test('should replace the inner HTML of element', function () { const testElement = mockP5Prototype.createElement('div'); const initialtestHTML = '

    Hello World

    '; const modifiedtestHTML = '

    Hello World !!!

    '; @@ -1319,7 +1330,7 @@ suite('DOM', function() { assert.deepEqual(testElement.elt.innerHTML, modifiedtestHTML); }); - test('should append to the inner HTML if second param is true', function() { + test('should append to the inner HTML if second param is true', function () { const testElement = mockP5Prototype.createElement('div'); const testHTML1 = '

    Hello World

    '; const testHTML2 = '

    Hello World !!!

    '; @@ -1331,7 +1342,7 @@ suite('DOM', function() { assert.deepEqual(testElement.elt.innerHTML, testHTML1 + testHTML2); }); - test('should replace the inner HTML if second param is false', function() { + test('should replace the inner HTML if second param is false', function () { const testElement = mockP5Prototype.createElement('div'); const testHTML1 = '

    Hello World

    '; const testHTML2 = '

    Hello World !!!

    '; @@ -1345,31 +1356,31 @@ suite('DOM', function() { }); // p5.Element.prototype.position - suite('p5.Element.prototype.position', function() { - afterEach(function() { + suite('p5.Element.prototype.position', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { // Create any p5.Element const testElement = mockP5Prototype.createElement('a'); assert.isFunction(testElement.position); }); - test('should return current position if no args are given', function() { + test('should return current position if no args are given', function () { const testElement = mockP5Prototype.createButton('testButton'); const position = testElement.position(); assert.deepEqual(position.x, testElement.elt.offsetLeft); assert.deepEqual(position.y, testElement.elt.offsetTop); }); - test('should set default position as absolute', function() { + test('should set default position as absolute', function () { const testElement = mockP5Prototype.createButton('testButton'); testElement.position(20, 70); assert.deepEqual(testElement.elt.style.position, 'absolute'); }); - test('should set given params as properties', function() { + test('should set given params as properties', function () { const testElement = mockP5Prototype.createButton('testButton'); testElement.position(20, 80, 'static'); diff --git a/test/unit/dom/p5.Element.js b/test/unit/dom/p5.Element.js index 5d744a7511..0bc859a591 100644 --- a/test/unit/dom/p5.Element.js +++ b/test/unit/dom/p5.Element.js @@ -2,7 +2,7 @@ import { mockP5, mockP5Prototype } from '../../js/mocks'; import dom from '../../../src/dom/dom'; -suite('p5.Element', function() { +suite('p5.Element', function () { // const mockP5Prototype = new p5(function(sketch) { // sketch.setup = function() {}; // sketch.draw = function() {}; @@ -22,7 +22,7 @@ suite('p5.Element', function() { dom(mockP5, mockP5Prototype); }); - suite('p5.Element.prototype.parent', function() { + suite('p5.Element.prototype.parent', function () { let div0, div1; beforeEach(() => { @@ -35,25 +35,25 @@ suite('p5.Element', function() { div1.remove(); }); - test('attaches child to parent', function() { + test('attaches child to parent', function () { div1.attribute('id', 'child'); div1.parent(div0); //attaches div1 to div0 assert.equal(document.getElementById('child').parentElement, div0.elt); }); - test('attaches child to parent using classname', function() { + test('attaches child to parent using classname', function () { div0.attribute('id', 'parent'); div1.parent('parent'); //attaches div1 to div0 using classname assert.equal(div1.parent(), div0.elt); //returns parent of div1 }); - test('attaches child to parent using id', function() { + test('attaches child to parent using id', function () { div0.attribute('id', 'parent'); div1.parent('#parent'); //attaches div1 to div0 assert.equal(div1.parent(), div0.elt); //returns parent of div1 using id }); - test('returns the parent', function() { + test('returns the parent', function () { let div0 = document.createElement('div'); let div1 = document.createElement('div'); div1.setAttribute('id', 'child'); @@ -63,14 +63,14 @@ suite('p5.Element', function() { }); }); - suite('p5.Element.prototype.id', function() { - test('attaches child to parent', function() { + suite('p5.Element.prototype.id', function () { + test('attaches child to parent', function () { const elt = mockP5Prototype.createDiv(); elt.id('test'); assert.equal(document.getElementById('test'), elt.elt); }); - test('returns the id', function() { + test('returns the id', function () { const elt = document.createElement('div'); elt.setAttribute('id', 'test'); document.body.appendChild(elt); @@ -78,12 +78,12 @@ suite('p5.Element', function() { }); }); - suite.todo('p5.Element.prototype.mousePressed', function() { - test('attaches and gets events', function() { + suite.todo('p5.Element.prototype.mousePressed', function () { + test('attaches and gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -93,15 +93,15 @@ suite('p5.Element', function() { assert.equal(myFnCounter, 1); }); - test('attaches multiple handlers and only latest gets events', function() { + test('attaches multiple handlers and only latest gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; var myFnCounterOther = 0; - var myFnOther = function() { + var myFnOther = function () { myFnCounterOther++; }; @@ -114,12 +114,12 @@ suite('p5.Element', function() { }); }); - suite('p5.Element.prototype.mouseClicked', function() { - test('attaches and gets events', function() { + suite('p5.Element.prototype.mouseClicked', function () { + test('attaches and gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -129,15 +129,15 @@ suite('p5.Element', function() { assert.equal(myFnCounter, 1); }); - test('attaches multiple handlers and only latest gets events', function() { + test('attaches multiple handlers and only latest gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; var myFnCounterOther = 0; - var myFnOther = function() { + var myFnOther = function () { myFnCounterOther++; }; @@ -149,11 +149,11 @@ suite('p5.Element', function() { assert.equal(myFnCounterOther, 1); }); - test('detaches and does not get events', function() { + test('detaches and does not get events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -165,12 +165,12 @@ suite('p5.Element', function() { }); }); - suite('p5.Element.prototype.doubleClicked', function() { - test('attaches and gets events', function() { + suite('p5.Element.prototype.doubleClicked', function () { + test('attaches and gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -180,15 +180,15 @@ suite('p5.Element', function() { assert.equal(myFnCounter, 1); }); - test('attaches multiple handlers and only latest gets events', function() { + test('attaches multiple handlers and only latest gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; var myFnCounterOther = 0; - var myFnOther = function() { + var myFnOther = function () { myFnCounterOther++; }; @@ -200,11 +200,11 @@ suite('p5.Element', function() { assert.equal(myFnCounterOther, 1); }); - test('detaches and does not get events', function() { + test('detaches and does not get events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -216,12 +216,12 @@ suite('p5.Element', function() { }); }); - suite('p5.Element.prototype.mouseWheel', function() { - test('attaches and gets events', function() { + suite('p5.Element.prototype.mouseWheel', function () { + test('attaches and gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function(event) { + var myFn = function (event) { if (event.deltaX > 0) { myFnCounter++; } @@ -233,15 +233,15 @@ suite('p5.Element', function() { assert.equal(myFnCounter, 1); }); - test('attaches multiple handlers and only latest gets events', function() { + test('attaches multiple handlers and only latest gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; var myFnCounterOther = 0; - var myFnOther = function() { + var myFnOther = function () { myFnCounterOther++; }; @@ -254,12 +254,12 @@ suite('p5.Element', function() { }); }); - suite('p5.Element.prototype.mouseReleased', function() { - test('attaches and gets events', function() { + suite('p5.Element.prototype.mouseReleased', function () { + test('attaches and gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -269,15 +269,15 @@ suite('p5.Element', function() { assert.equal(myFnCounter, 1); }); - test('attaches multiple handlers and only latest gets events', function() { + test('attaches multiple handlers and only latest gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; var myFnCounterOther = 0; - var myFnOther = function() { + var myFnOther = function () { myFnCounterOther++; }; @@ -289,11 +289,11 @@ suite('p5.Element', function() { assert.equal(myFnCounterOther, 1); }); - test('detaches and does not get events', function() { + test('detaches and does not get events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -305,12 +305,12 @@ suite('p5.Element', function() { }); }); - suite('p5.Element.prototype.mouseMoved', function() { - test('attaches and gets events', function() { + suite('p5.Element.prototype.mouseMoved', function () { + test('attaches and gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -320,15 +320,15 @@ suite('p5.Element', function() { assert.equal(myFnCounter, 1); }); - test('attaches multiple handlers and only latest gets events', function() { + test('attaches multiple handlers and only latest gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; var myFnCounterOther = 0; - var myFnOther = function() { + var myFnOther = function () { myFnCounterOther++; }; @@ -340,11 +340,11 @@ suite('p5.Element', function() { assert.equal(myFnCounterOther, 1); }); - test('detaches and does not get events', function() { + test('detaches and does not get events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -356,12 +356,12 @@ suite('p5.Element', function() { }); }); - suite('p5.Element.prototype.mouseOver', function() { - test('attaches and gets events', function() { + suite('p5.Element.prototype.mouseOver', function () { + test('attaches and gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -371,15 +371,15 @@ suite('p5.Element', function() { assert.equal(myFnCounter, 1); }); - test('attaches multiple handlers and only latest gets events', function() { + test('attaches multiple handlers and only latest gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; var myFnCounterOther = 0; - var myFnOther = function() { + var myFnOther = function () { myFnCounterOther++; }; @@ -391,11 +391,11 @@ suite('p5.Element', function() { assert.equal(myFnCounterOther, 1); }); - test('detaches and does not get events', function() { + test('detaches and does not get events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -407,12 +407,12 @@ suite('p5.Element', function() { }); }); - suite('p5.Element.prototype.mouseOut', function() { - test('attaches and gets events', function() { + suite('p5.Element.prototype.mouseOut', function () { + test('attaches and gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -422,15 +422,15 @@ suite('p5.Element', function() { assert.equal(myFnCounter, 1); }); - test('attaches multiple handlers and only latest gets events', function() { + test('attaches multiple handlers and only latest gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; var myFnCounterOther = 0; - var myFnOther = function() { + var myFnOther = function () { myFnCounterOther++; }; @@ -442,11 +442,11 @@ suite('p5.Element', function() { assert.equal(myFnCounterOther, 1); }); - test('detaches and does not get events', function() { + test('detaches and does not get events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -458,11 +458,11 @@ suite('p5.Element', function() { }); }); - suite('p5.Element.prototype.dragOver', function() { - test('attaches and gets events', function() { + suite('p5.Element.prototype.dragOver', function () { + test('attaches and gets events', function () { const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -472,15 +472,15 @@ suite('p5.Element', function() { assert.equal(myFnCounter, 1); }); - test('attaches multiple handlers and only latest gets events', function() { + test('attaches multiple handlers and only latest gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; var myFnCounterOther = 0; - var myFnOther = function() { + var myFnOther = function () { myFnCounterOther++; }; @@ -492,11 +492,11 @@ suite('p5.Element', function() { assert.equal(myFnCounterOther, 1); }); - test('detaches and does not get events', function() { + test('detaches and does not get events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -508,11 +508,11 @@ suite('p5.Element', function() { }); }); - suite('p5.Element.prototype.dragLeave', function() { - test('attaches and gets events', function() { + suite('p5.Element.prototype.dragLeave', function () { + test('attaches and gets events', function () { const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -522,15 +522,15 @@ suite('p5.Element', function() { assert.equal(myFnCounter, 1); }); - test('attaches multiple handlers and only latest gets events', function() { + test('attaches multiple handlers and only latest gets events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; var myFnCounterOther = 0; - var myFnOther = function() { + var myFnOther = function () { myFnCounterOther++; }; @@ -542,11 +542,11 @@ suite('p5.Element', function() { assert.equal(myFnCounterOther, 1); }); - test('detaches and does not get events', function() { + test('detaches and does not get events', function () { // setup const elt = mockP5Prototype.createDiv('hello'); var myFnCounter = 0; - var myFn = function() { + var myFn = function () { myFnCounter++; }; @@ -558,7 +558,7 @@ suite('p5.Element', function() { }); }); - suite('operating with element classes', function() { + suite('operating with element classes', function () { let elt; beforeEach(() => { @@ -569,7 +569,7 @@ suite('p5.Element', function() { elt.remove(); }); - test('should add class to element', function() { + test('should add class to element', function () { elt.setAttribute('id', 'testdiv'); document.body.appendChild(elt); @@ -577,7 +577,7 @@ suite('p5.Element', function() { assert.strictEqual(elt.getAttribute('class'), 'testclass'); }); - test('should remove class from element with only one class', function() { + test('should remove class from element with only one class', function () { elt.setAttribute('id', 'testdiv'); elt.setAttribute('class', 'testclass'); document.body.appendChild(elt); @@ -586,7 +586,7 @@ suite('p5.Element', function() { assert.strictEqual(elt.getAttribute('class'), ''); }); - test('should remove class from element with several classes', function() { + test('should remove class from element with several classes', function () { elt.setAttribute('id', 'testdiv'); elt.setAttribute('class', 'testclass1 testclass2 testclass3'); document.body.appendChild(elt); @@ -595,34 +595,52 @@ suite('p5.Element', function() { assert.strictEqual(elt.getAttribute('class'), 'testclass1 testclass3'); }); - test('should return true if element has specified class', function() { + test('should return true if element has specified class', function () { elt.setAttribute('id', 'testdiv'); elt.setAttribute('class', 'testclass1 testclass2 testclass3'); document.body.appendChild(elt); - assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('testclass2'), true); + assert.strictEqual( + mockP5Prototype.select('#testdiv').hasClass('testclass2'), + true + ); }); - test('should return false if element has not specified class', function() { + test('should return false if element has not specified class', function () { elt.setAttribute('id', 'testdiv'); elt.setAttribute('class', 'testclass1 testclass3'); document.body.appendChild(elt); - assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('testclass2'), false); + assert.strictEqual( + mockP5Prototype.select('#testdiv').hasClass('testclass2'), + false + ); }); - test('should return false if element has class that is partially similar as specified class', function() { + test('should return false if element has class that is partially similar as specified class', function () { elt.setAttribute('id', 'testdiv'); elt.setAttribute('class', 'testclass slideshow newtestsclas'); document.body.appendChild(elt); - assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('show'), false); - assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('slide'), false); - assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('test'), false); - assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('class'), false); - }); - - test('should toggle specified class on element', function() { + assert.strictEqual( + mockP5Prototype.select('#testdiv').hasClass('show'), + false + ); + assert.strictEqual( + mockP5Prototype.select('#testdiv').hasClass('slide'), + false + ); + assert.strictEqual( + mockP5Prototype.select('#testdiv').hasClass('test'), + false + ); + assert.strictEqual( + mockP5Prototype.select('#testdiv').hasClass('class'), + false + ); + }); + + test('should toggle specified class on element', function () { elt.setAttribute('id', 'testdiv'); elt.setAttribute('class', 'testclass1 testclass2'); document.body.appendChild(elt); diff --git a/test/unit/dom/p5.MediaElement.js b/test/unit/dom/p5.MediaElement.js index 38718c9821..6e10b59d56 100644 --- a/test/unit/dom/p5.MediaElement.js +++ b/test/unit/dom/p5.MediaElement.js @@ -1,6 +1,9 @@ import { vi } from 'vitest'; import { mockP5, mockP5Prototype } from '../../js/mocks'; -import { default as media, MediaElement } from '../../../src/dom/p5.MediaElement'; +import { + default as media, + MediaElement +} from '../../../src/dom/p5.MediaElement'; import { Element } from '../../../src/dom/p5.Element'; import { default as pixels } from '../../../src/image/pixels'; @@ -8,7 +11,8 @@ suite('p5.MediaElement', () => { beforeAll(() => { media(mockP5, mockP5Prototype); pixels(mockP5, mockP5Prototype); - navigator.mediaDevices.getUserMedia = vi.fn() + navigator.mediaDevices.getUserMedia = vi + .fn() .mockResolvedValue('stream-value'); }); @@ -16,8 +20,8 @@ suite('p5.MediaElement', () => { vi.restoreAllMocks(); }); - suite('p5.prototype.createVideo', function() { - afterEach(function() { + suite('p5.prototype.createVideo', function () { + afterEach(function () { document.body.innerHTML = ''; }); @@ -26,17 +30,17 @@ suite('p5.MediaElement', () => { '/test/unit/assets/target.gif' ]; - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createVideo); }); - test('should return p5.Element of HTMLVideoElement', function() { + test('should return p5.Element of HTMLVideoElement', function () { const testElement = mockP5Prototype.createVideo(''); assert.instanceOf(testElement, MediaElement); assert.instanceOf(testElement.elt, HTMLVideoElement); }); - test('should accept a singular media source', function() { + test('should accept a singular media source', function () { const mediaSource = mediaSources[0]; const testElement = mockP5Prototype.createVideo(mediaSource); const sourceEl = testElement.elt.children[0]; @@ -46,7 +50,7 @@ suite('p5.MediaElement', () => { assert.isTrue(sourceEl.src.endsWith(mediaSource)); }); - test('should accept multiple media sources', function() { + test('should accept multiple media sources', function () { const testElement = mockP5Prototype.createVideo(mediaSources); assert.deepEqual(testElement.elt.childElementCount, mediaSources.length); @@ -68,7 +72,7 @@ suite('p5.MediaElement', () => { // ); // TODO: integration test - test.todo('should work with tint()', function(done) { + test.todo('should work with tint()', function (done) { const imgElt = myp5.createImg('/test/unit/assets/cat.jpg', ''); const testElement = myp5.createVideo('/test/unit/assets/cat.webm', () => { // Workaround for headless tests, where the video data isn't loading @@ -92,7 +96,7 @@ suite('p5.MediaElement', () => { }); }); - test.todo('should work with updatePixels()', function(done) { + test.todo('should work with updatePixels()', function (done) { let loaded = false; let prevElt; const imgElt = myp5.createImg('/test/unit/assets/cat.jpg', ''); @@ -105,7 +109,7 @@ suite('p5.MediaElement', () => { }); let drewUpdatedPixels = false; - myp5.draw = function() { + myp5.draw = function () { if (!loaded) return; myp5.background(255); @@ -137,8 +141,9 @@ suite('p5.MediaElement', () => { myp5.loadPixels(); testElement.loadPixels(); - expect([...testElement.pixels.slice(0, 4)]) - .to.not.deep.equal([255, 0, 0, 255]); + expect([...testElement.pixels.slice(0, 4)]).to.not.deep.equal([ + 255, 0, 0, 255 + ]); assert.deepEqual( [...myp5.pixels.slice(0, 4)], [...testElement.pixels.slice(0, 4)] @@ -151,8 +156,8 @@ suite('p5.MediaElement', () => { }); }); - suite('p5.prototype.createAudio', function() { - afterEach(function() { + suite('p5.prototype.createAudio', function () { + afterEach(function () { document.body.innerHTML = ''; }); @@ -161,17 +166,17 @@ suite('p5.MediaElement', () => { '/test/unit/assets/beat.mp3' ]; - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createAudio); }); - test('should return p5.Element of HTMLAudioElement', function() { + test('should return p5.Element of HTMLAudioElement', function () { const testElement = mockP5Prototype.createAudio(''); assert.instanceOf(testElement, MediaElement); assert.instanceOf(testElement.elt, HTMLAudioElement); }); - test('should accept a singular media source', function() { + test('should accept a singular media source', function () { const mediaSource = mediaSources[0]; const testElement = mockP5Prototype.createAudio(mediaSource); const sourceEl = testElement.elt.children[0]; @@ -181,7 +186,7 @@ suite('p5.MediaElement', () => { assert.isTrue(sourceEl.src.endsWith(mediaSource)); }); - test('should accept multiple media sources', function() { + test('should accept multiple media sources', function () { const testElement = mockP5Prototype.createAudio(mediaSources); assert.deepEqual(testElement.elt.childElementCount, mediaSources.length); @@ -203,16 +208,16 @@ suite('p5.MediaElement', () => { // ); }); - suite('p5.prototype.createCapture', function() { - afterEach(function() { + suite('p5.prototype.createCapture', function () { + afterEach(function () { document.body.innerHTML = ''; }); - test('should be a function', function() { + test('should be a function', function () { assert.isFunction(mockP5Prototype.createCapture); }); - test('should return p5.Element of video type', function() { + test('should return p5.Element of video type', function () { const testElement = mockP5Prototype.createCapture(mockP5Prototype.VIDEO); assert.instanceOf(testElement, Element); assert.instanceOf(testElement.elt, HTMLVideoElement); @@ -231,7 +236,7 @@ suite('p5.MediaElement', () => { // ); // Required for ios 11 devices - test('should have playsinline attribute to empty string on DOM element', function() { + test('should have playsinline attribute to empty string on DOM element', function () { const testElement = mockP5Prototype.createCapture(mockP5Prototype.VIDEO); // Weird check, setter accepts : playinline, getter accepts playInline assert.isTrue(testElement.elt.playsInline); @@ -248,8 +253,10 @@ suite('p5.MediaElement', () => { document.body.innerHTML = ''; }); - test('should not throw an error', function() { - const testElement = mockP5Prototype.createVideo('/test/unit/assets/nyan_cat.gif'); + test('should not throw an error', function () { + const testElement = mockP5Prototype.createVideo( + '/test/unit/assets/nyan_cat.gif' + ); assert.doesNotThrow(() => { testElement.copy(0, 0, 10, 10, 0, 0, 10, 10); }); diff --git a/test/unit/events/acceleration.js b/test/unit/events/acceleration.js index 6918ba1d1a..7ab51d1529 100644 --- a/test/unit/events/acceleration.js +++ b/test/unit/events/acceleration.js @@ -1,17 +1,17 @@ import p5 from '../../../src/app.js'; -suite('Acceleration Events', function() { +suite('Acceleration Events', function () { var myp5; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); @@ -41,76 +41,76 @@ suite('Acceleration Events', function() { } ); - suite('acceleration', function() { - test('accelerationX should be 20', function() { + suite('acceleration', function () { + test('accelerationX should be 20', function () { window.dispatchEvent(deviceMotionEvent1); assert.strictEqual(myp5.accelerationX, 20); }); - test('accelerationY should be 40', function() { + test('accelerationY should be 40', function () { window.dispatchEvent(deviceMotionEvent1); assert.strictEqual(myp5.accelerationY, 40); }); - test('accelerationZ should be 60', function() { + test('accelerationZ should be 60', function () { window.dispatchEvent(deviceMotionEvent1); assert.strictEqual(myp5.accelerationZ, 60); }); }); - suite('previous acceleration', function() { - test('pAccelerationX should be 20', function() { + suite('previous acceleration', function () { + test('pAccelerationX should be 20', function () { window.dispatchEvent(deviceMotionEvent1); window.dispatchEvent(deviceMotionEvent2); assert.strictEqual(myp5.pAccelerationX, 20); }); - test('pAccelerationY should be 40', function() { + test('pAccelerationY should be 40', function () { window.dispatchEvent(deviceMotionEvent1); window.dispatchEvent(deviceMotionEvent2); assert.strictEqual(myp5.pAccelerationY, 40); }); - test('pAccelerationZ should be 60', function() { + test('pAccelerationZ should be 60', function () { window.dispatchEvent(deviceMotionEvent1); window.dispatchEvent(deviceMotionEvent2); assert.strictEqual(myp5.pAccelerationZ, 60); }); }); - suite('rotation', function() { - test('rotationX should be 45', function() { + suite('rotation', function () { + test('rotationX should be 45', function () { window.dispatchEvent(deviceOrientationEvent1); assert.strictEqual(myp5.rotationX, 45 * (Math.PI / 180.0)); }); - test('rotationY should be 90', function() { + test('rotationY should be 90', function () { window.dispatchEvent(deviceOrientationEvent1); assert.strictEqual(myp5.rotationY, 90 * (Math.PI / 180.0)); }); - test('rotationZ should be 10', function() { + test('rotationZ should be 10', function () { window.dispatchEvent(deviceOrientationEvent1); assert.strictEqual(myp5.rotationZ, 10 * (Math.PI / 180.0)); }); }); - suite('previous rotation', function() { - test('pRotationX should be 45', function() { + suite('previous rotation', function () { + test('pRotationX should be 45', function () { window.dispatchEvent(deviceOrientationEvent1); window.dispatchEvent(deviceOrientationEvent2); assert.strictEqual(myp5.pRotationX, 45 * (Math.PI / 180.0)); }); - test('pRotationY should be 90', function() { + test('pRotationY should be 90', function () { window.dispatchEvent(deviceOrientationEvent1); window.dispatchEvent(deviceOrientationEvent2); assert.strictEqual(myp5.pRotationY, 90 * (Math.PI / 180.0)); }); - test('pRotationZ should be 10', function() { + test('pRotationZ should be 10', function () { window.dispatchEvent(deviceOrientationEvent1); window.dispatchEvent(deviceOrientationEvent2); assert.strictEqual(myp5.pRotationZ, 10 * (Math.PI / 180.0)); }); }); - suite.todo('deviceMoved', function() { - test('deviceMoved must run when device is moved more than the threshold value', function() { + suite.todo('deviceMoved', function () { + test('deviceMoved must run when device is moved more than the threshold value', function () { let count = 0; - myp5.deviceMoved = function() { + myp5.deviceMoved = function () { count += 1; }; window.dispatchEvent( @@ -119,9 +119,9 @@ suite('Acceleration Events', function() { assert.strictEqual(count, 1); }); - test('deviceMoved should not run when device is moved less than the threshold value', function() { + test('deviceMoved should not run when device is moved less than the threshold value', function () { let count = 0; - myp5.deviceMoved = function() { + myp5.deviceMoved = function () { count += 1; }; window.dispatchEvent( @@ -131,9 +131,9 @@ suite('Acceleration Events', function() { assert.strictEqual(count, 0); }); - test('p5.prototype.setMoveThreshold', function() { + test('p5.prototype.setMoveThreshold', function () { let count = 0; - myp5.deviceMoved = function() { + myp5.deviceMoved = function () { count += 1; }; myp5.setMoveThreshold(0.1); @@ -145,10 +145,10 @@ suite('Acceleration Events', function() { }); }); - suite.todo('deviceTurned', function() { - test('deviceTurned must run when device is turned more than 90 degrees', function() { + suite.todo('deviceTurned', function () { + test('deviceTurned must run when device is turned more than 90 degrees', function () { let count = 0; - myp5.deviceTurned = function() { + myp5.deviceTurned = function () { count += 1; }; window.dispatchEvent( @@ -160,9 +160,9 @@ suite('Acceleration Events', function() { assert.strictEqual(count, 1); }); - test('turnAxis should be X', function() { + test('turnAxis should be X', function () { let count = 0; - myp5.deviceTurned = function() { + myp5.deviceTurned = function () { if (myp5.turnAxis === 'X') count += 1; }; window.dispatchEvent( @@ -175,10 +175,10 @@ suite('Acceleration Events', function() { }); }); - suite.todo('deviceShaken', function() { - test('deviceShaken must run when device acceleration is more than the threshold value', function() { + suite.todo('deviceShaken', function () { + test('deviceShaken must run when device acceleration is more than the threshold value', function () { let count = 0; - myp5.deviceShaken = function() { + myp5.deviceShaken = function () { count += 1; }; window.dispatchEvent( @@ -187,9 +187,9 @@ suite('Acceleration Events', function() { assert.strictEqual(count, 1); }); - test('deviceMoved should not run when device acceleration is less than the threshold value', function() { + test('deviceMoved should not run when device acceleration is less than the threshold value', function () { let count = 0; - myp5.deviceShaken = function() { + myp5.deviceShaken = function () { count += 1; }; window.dispatchEvent( @@ -199,9 +199,9 @@ suite('Acceleration Events', function() { assert.strictEqual(count, 0); }); - test('p5.prototype.setShakeThreshold', function() { + test('p5.prototype.setShakeThreshold', function () { let count = 0; - myp5.deviceShaken = function() { + myp5.deviceShaken = function () { count += 1; }; myp5.setShakeThreshold(10); diff --git a/test/unit/events/keyboard.js b/test/unit/events/keyboard.js index e8280dc87d..db5431313c 100644 --- a/test/unit/events/keyboard.js +++ b/test/unit/events/keyboard.js @@ -2,81 +2,81 @@ import p5 from '../../../src/app.js'; import { isCode } from '../../../src/events/keyboard.js'; import { parallelSketches } from '../../js/p5_helpers'; -suite('Keyboard Events', function() { +suite('Keyboard Events', function () { var myp5; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('p5.prototype.keyIsPressed', function() { - test('keyIsPressed should be a boolean', function() { + suite('p5.prototype.keyIsPressed', function () { + test('keyIsPressed should be a boolean', function () { assert.isBoolean(myp5.keyIsPressed); }); - test('keyIsPressed should be true on key press', function() { + test('keyIsPressed should be true on key press', function () { window.dispatchEvent(new KeyboardEvent('keydown')); assert.strictEqual(myp5.keyIsPressed, true); }); - test('keyIsPressed should be true on multiple key presses', function() { + test('keyIsPressed should be true on multiple key presses', function () { window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Down' })); assert.strictEqual(myp5.keyIsPressed, true); }); - test('keyIsPressed should be false on key up', function() { + test('keyIsPressed should be false on key up', function () { window.dispatchEvent(new KeyboardEvent('keyup')); assert.strictEqual(myp5.keyIsPressed, false); }); }); - suite('p5.prototype.key', function() { - test('key should be a string', async function() { + suite('p5.prototype.key', function () { + test('key should be a string', async function () { window.dispatchEvent(new KeyboardEvent('keydown', { key: 's' })); assert.isString(myp5.key); }); - test.todo('key should return the key pressed', function() { + test.todo('key should return the key pressed', function () { window.dispatchEvent(new KeyboardEvent('keydown', { key: 'A' })); assert.strictEqual(myp5.key, 'A'); }); - test.todo('key should return the key pressed', function() { + test.todo('key should return the key pressed', function () { window.dispatchEvent(new KeyboardEvent('keydown', { key: '9' })); assert.strictEqual(myp5.key, '9'); }); - test.todo('key should return the key pressed', function() { + test.todo('key should return the key pressed', function () { window.dispatchEvent(new KeyboardEvent('keydown', { key: 'CapsLock' })); assert.strictEqual(myp5.key, 'CapsLock'); }); }); - suite('p5.prototype.keyCode', function() { - test('keyCode should be a number', function() { + suite('p5.prototype.keyCode', function () { + test('keyCode should be a number', function () { window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 65 })); assert.isNumber(myp5.keyCode); }); - test('key should return the key pressed', function() { + test('key should return the key pressed', function () { window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 65 })); assert.strictEqual(myp5.keyCode, 65); }); }); - suite('keyPressed', function() { - test.todo('keyPressed must run when key is pressed', function() { + suite('keyPressed', function () { + test.todo('keyPressed must run when key is pressed', function () { let count = 0; - myp5.keyPressed = function() { + myp5.keyPressed = function () { count += 1; }; window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 65 })); @@ -84,14 +84,14 @@ suite('Keyboard Events', function() { assert.strictEqual(count, 2); }); - test('keyPressed functions on multiple instances must run once', async function() { - let sketchFn = function(sketch, resolve, reject) { + test('keyPressed functions on multiple instances must run once', async function () { + let sketchFn = function (sketch, resolve, reject) { let count = 0; - sketch.keyPressed = function() { + sketch.keyPressed = function () { count += 1; }; - sketch.finish = function() { + sketch.finish = function () { resolve(count); }; }; @@ -104,25 +104,25 @@ suite('Keyboard Events', function() { }); }); - suite('keyReleased', function() { - test('keyReleased must run when key is released', function() { + suite('keyReleased', function () { + test('keyReleased must run when key is released', function () { let count = 0; - myp5.keyReleased = function() { + myp5.keyReleased = function () { count += 1; }; window.dispatchEvent(new KeyboardEvent('keyup')); assert.strictEqual(count, 1); }); - test('keyReleased functions on multiple instances must run once', async function() { - let sketchFn = function(sketch, resolve, reject) { + test('keyReleased functions on multiple instances must run once', async function () { + let sketchFn = function (sketch, resolve, reject) { let count = 0; - sketch.keyReleased = function() { + sketch.keyReleased = function () { count += 1; }; - sketch.finish = function() { + sketch.finish = function () { resolve(count); }; }; @@ -135,24 +135,24 @@ suite('Keyboard Events', function() { }); }); - suite('keyTyped', function() { - test('keyTyped must run when key is pressed', function() { + suite('keyTyped', function () { + test('keyTyped must run when key is pressed', function () { let count = 0; - myp5.keyTyped = function() { + myp5.keyTyped = function () { count += 1; }; window.dispatchEvent(new KeyboardEvent('keypress')); assert.strictEqual(count, 1); }); - test('keyTyped functions on multiple instances must run once', async function() { - let sketchFn = function(sketch, resolve, reject) { + test('keyTyped functions on multiple instances must run once', async function () { + let sketchFn = function (sketch, resolve, reject) { let count = 0; - sketch.keyTyped = function() { + sketch.keyTyped = function () { count += 1; }; - sketch.finish = function() { + sketch.finish = function () { resolve(count); }; }; @@ -165,8 +165,8 @@ suite('Keyboard Events', function() { }); }); - suite('isCode', function() { - test('returns false for non-string inputs', function() { + suite('isCode', function () { + test('returns false for non-string inputs', function () { assert.isFalse(isCode(null)); assert.isFalse(isCode(undefined)); assert.isFalse(isCode(123)); @@ -174,7 +174,7 @@ suite('Keyboard Events', function() { assert.isFalse(isCode([])); }); - test('returns false for single non-digit and digit characters', function() { + test('returns false for single non-digit and digit characters', function () { assert.isFalse(isCode('a')); assert.isFalse(isCode('Z')); assert.isFalse(isCode('1')); @@ -182,51 +182,54 @@ suite('Keyboard Events', function() { assert.isFalse(isCode(' ')); }); - test('returns true for multi-character strings', function() { + test('returns true for multi-character strings', function () { assert.isTrue(isCode('ShiftLeft')); assert.isTrue(isCode('ArrowUp')); assert.isTrue(isCode('ab')); }); - test('returns false/true for apppropriate leftright keys and codes', function() { + test('returns false/true for apppropriate leftright keys and codes', function () { assert.isFalse(isCode('Alt')); assert.isFalse(isCode('Shift')); assert.isTrue(isCode('AltLeft')); assert.isTrue(isCode('ShiftLeft')); }); - test('handles edge cases correctly', function() { - assert.isFalse(isCode('')); // empty string + test('handles edge cases correctly', function () { + assert.isFalse(isCode('')); // empty string assert.isTrue(isCode('11')); // multi-digit number assert.isTrue(isCode('1a')); // digit + letter }); }); - suite('p5.prototype.keyIsDown', function() { - test('keyIsDown should return a boolean', function() { + suite('p5.prototype.keyIsDown', function () { + test('keyIsDown should return a boolean', function () { assert.isBoolean(myp5.keyIsDown('a')); assert.isBoolean(myp5.keyIsDown('Enter')); }); - test('keyIsDown should return true if key is down', function() { + test('keyIsDown should return true if key is down', function () { // Test single character keys window.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' })); assert.strictEqual(myp5.keyIsDown('a'), true); // Test digit keys - window.dispatchEvent(new KeyboardEvent('keydown', { key: '1', code: 'Digit1' })); + window.dispatchEvent( + new KeyboardEvent('keydown', { key: '1', code: 'Digit1' }) + ); assert.strictEqual(myp5.keyIsDown('1'), true); // Test special keys - window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter' })); + window.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter' }) + ); assert.strictEqual(myp5.keyIsDown('Enter'), true); }); - test('keyIsDown should return false if key is not down', function() { + test('keyIsDown should return false if key is not down', function () { // Ensure key is not down window.dispatchEvent(new KeyboardEvent('keyup')); assert.strictEqual(myp5.keyIsDown('z'), false); - }); }); }); diff --git a/test/unit/events/mouse.js b/test/unit/events/mouse.js index 0e02ece634..392ca7c165 100644 --- a/test/unit/events/mouse.js +++ b/test/unit/events/mouse.js @@ -1,7 +1,7 @@ import p5 from '../../../src/app.js'; import { parallelSketches } from '../../js/p5_helpers'; -suite.todo('Mouse Events', function() { +suite.todo('Mouse Events', function () { let myp5; let canvas; @@ -13,9 +13,9 @@ suite.todo('Mouse Events', function() { let touchEvent1; let touchEvent2; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; canvas = myp5._curElement.elt; rect = canvas.getBoundingClientRect(); @@ -43,82 +43,88 @@ suite.todo('Mouse Events', function() { }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - let mouseEvent1 = new PointerEvent('pointermove', { clientX: 100, clientY: 100 }); - let mouseEvent2 = new PointerEvent('pointermove', { clientX: 200, clientY: 200 }); + let mouseEvent1 = new PointerEvent('pointermove', { + clientX: 100, + clientY: 100 + }); + let mouseEvent2 = new PointerEvent('pointermove', { + clientX: 200, + clientY: 200 + }); - suite('p5.prototype._hasMouseInteracted', function() { - test('_hasMouseInteracted should be a boolean', function() { + suite('p5.prototype._hasMouseInteracted', function () { + test('_hasMouseInteracted should be a boolean', function () { assert.isBoolean(myp5._hasMouseInteracted); }); - test('_hasMouseInteracted should be false before mouse interaction', function() { + test('_hasMouseInteracted should be false before mouse interaction', function () { assert.strictEqual(myp5._hasMouseInteracted, false); }); - test('_hasMouseInteracted should be true on mouse interaction', function() { + test('_hasMouseInteracted should be true on mouse interaction', function () { window.dispatchEvent(mouseEvent1); assert.strictEqual(myp5._hasMouseInteracted, true); }); - test('_hasMouseInteracted should be true on touch interaction', function() { + test('_hasMouseInteracted should be true on touch interaction', function () { window.dispatchEvent(touchEvent1); assert.strictEqual(myp5._hasMouseInteracted, true); }); }); - suite('p5.prototype.mouseX', function() { - test('mouseX should be a number', function() { + suite('p5.prototype.mouseX', function () { + test('mouseX should be a number', function () { assert.isNumber(myp5.mouseX); }); - test('mouseX should be current horizontal position of mouse relative to the canvas', function() { + test('mouseX should be current horizontal position of mouse relative to the canvas', function () { let result = (mouseEvent1.clientX - rect.left) / sx; window.dispatchEvent(mouseEvent1); assert.strictEqual(myp5.mouseX, result); }); - test('mouseX should be current horizontal position of touch relative to the canvas', function() { + test('mouseX should be current horizontal position of touch relative to the canvas', function () { let result = (touchObj1.clientX - rect.left) / sx; window.dispatchEvent(touchEvent1); assert.strictEqual(myp5.mouseX, result); }); }); - suite('p5.prototype.mouseY', function() { - test('mouseY should be a number', function() { + suite('p5.prototype.mouseY', function () { + test('mouseY should be a number', function () { assert.isNumber(myp5.mouseY); }); - test('mouseY should be current vertical position of mouse relative to the canvas', function() { + test('mouseY should be current vertical position of mouse relative to the canvas', function () { let result = (mouseEvent1.clientY - rect.top) / sy; window.dispatchEvent(mouseEvent1); assert.strictEqual(myp5.mouseY, result); }); - test('mouseY should be current vertical position of touch relative to the canvas', function() { + test('mouseY should be current vertical position of touch relative to the canvas', function () { let result = (touchObj1.clientY - rect.top) / sy; window.dispatchEvent(touchEvent1); assert.strictEqual(myp5.mouseY, result); }); }); - suite('p5.prototype.pmouseX', function() { - test('pmouseX should be a number', function() { + suite('p5.prototype.pmouseX', function () { + test('pmouseX should be a number', function () { assert.isNumber(myp5.pmouseX); }); - test('pmouseX should be previous horizontal position of mouse relative to the canvas', function() { + test('pmouseX should be previous horizontal position of mouse relative to the canvas', function () { window.dispatchEvent(mouseEvent1); // dispatch first mouse event window.dispatchEvent(mouseEvent2); // dispatch second mouse event let result = (mouseEvent1.clientX - rect.left) / sx; assert.strictEqual(myp5.pmouseX, result); }); - test('pmouseX should be previous horizontal position of touch relative to the canvas', function() { + test('pmouseX should be previous horizontal position of touch relative to the canvas', function () { window.dispatchEvent(touchEvent1); // dispatch first touch event window.dispatchEvent(touchEvent2); // dispatch second touch event let result = (touchObj1.clientX - rect.left) / sx; @@ -126,133 +132,133 @@ suite.todo('Mouse Events', function() { }); }); - suite('p5.prototype.pmouseY', function() { - test('pmouseY should be a number', function() { + suite('p5.prototype.pmouseY', function () { + test('pmouseY should be a number', function () { assert.isNumber(myp5.pmouseY); }); - test('pmouseY should be previous vertical position of mouse relative to the canvas', function() { + test('pmouseY should be previous vertical position of mouse relative to the canvas', function () { window.dispatchEvent(mouseEvent1); // dispatch first mouse event window.dispatchEvent(mouseEvent2); // dispatch second mouse event let result = (mouseEvent1.clientY - rect.top) / sy; assert.strictEqual(myp5.pmouseY, result); }); - test('pmouseY should be previous vertical position of touch relative to the canvas', function() { + test('pmouseY should be previous vertical position of touch relative to the canvas', function () { window.dispatchEvent(touchEvent1); // dispatch first touch event window.dispatchEvent(touchEvent2); // dispatch second touch event let result = (touchObj1.clientY - rect.top) / sy; assert.strictEqual(myp5.pmouseY, result); }); }); - suite('p5.prototype.winMouseX', function() { - test('winMouseX should be a number', function() { + suite('p5.prototype.winMouseX', function () { + test('winMouseX should be a number', function () { assert.isNumber(myp5.winMouseX); }); - test('winMouseX should be current horizontal position of mouse relative to the window', function() { + test('winMouseX should be current horizontal position of mouse relative to the window', function () { window.dispatchEvent(mouseEvent1); assert.strictEqual(myp5.winMouseX, mouseEvent1.clientX); }); - test('winMouseX should be current horizontal position of touch relative to the window', function() { + test('winMouseX should be current horizontal position of touch relative to the window', function () { window.dispatchEvent(touchEvent1); assert.strictEqual(myp5.winMouseX, touchObj1.clientX); }); }); - suite('p5.prototype.winMouseY', function() { - test('winMouseY should be a number', function() { + suite('p5.prototype.winMouseY', function () { + test('winMouseY should be a number', function () { assert.isNumber(myp5.winMouseY); }); - test('winMouseY should be current vertical position of mouse relative to the window', function() { + test('winMouseY should be current vertical position of mouse relative to the window', function () { window.dispatchEvent(mouseEvent1); assert.strictEqual(myp5.winMouseY, mouseEvent1.clientY); }); - test('winMouseY should be current vertical position of touch relative to the window', function() { + test('winMouseY should be current vertical position of touch relative to the window', function () { window.dispatchEvent(touchEvent1); assert.strictEqual(myp5.winMouseY, touchObj1.clientY); }); }); - suite('p5.prototype.pwinMouseX', function() { - test('pwinMouseX should be a number', function() { + suite('p5.prototype.pwinMouseX', function () { + test('pwinMouseX should be a number', function () { assert.isNumber(myp5.pwinMouseX); }); - test('pwinMouseX should be previous horizontal position of mouse relative to the window', function() { + test('pwinMouseX should be previous horizontal position of mouse relative to the window', function () { window.dispatchEvent(mouseEvent1); // dispatch first mouse event window.dispatchEvent(mouseEvent2); // dispatch second mouse event assert.strictEqual(myp5.pwinMouseX, mouseEvent1.clientX); }); - test('pwinMouseX should be previous horizontal position of touch relative to the window', function() { + test('pwinMouseX should be previous horizontal position of touch relative to the window', function () { window.dispatchEvent(touchEvent1); // dispatch first touch event window.dispatchEvent(touchEvent2); // dispatch second touch event assert.strictEqual(myp5.pwinMouseX, touchObj1.clientX); }); }); - suite('p5.prototype.pwinMouseY', function() { - test('pwinMouseY should be a number', function() { + suite('p5.prototype.pwinMouseY', function () { + test('pwinMouseY should be a number', function () { assert.isNumber(myp5.pwinMouseY); }); - test('pwinMouseY should be previous vertical position of mouse relative to the window', async function() { + test('pwinMouseY should be previous vertical position of mouse relative to the window', async function () { window.dispatchEvent(mouseEvent1); // dispatch first mouse event window.dispatchEvent(mouseEvent2); // dispatch second mouse event assert.strictEqual(myp5.pwinMouseY, mouseEvent1.clientY); }); - test('pwinMouseY should be previous vertical position of touch relative to the window', function() { + test('pwinMouseY should be previous vertical position of touch relative to the window', function () { window.dispatchEvent(touchEvent1); // dispatch first touch event window.dispatchEvent(touchEvent2); // dispatch second touch event assert.strictEqual(myp5.pwinMouseY, touchObj1.clientY); }); }); - suite('p5.prototype.mouseButton', function() { - test('_hasMouseInteracted should be a number', function() { + suite('p5.prototype.mouseButton', function () { + test('_hasMouseInteracted should be a number', function () { assert.isNumber(myp5.mouseButton); }); - test('mouseButton should 0 for uninitialised', function() { + test('mouseButton should 0 for uninitialised', function () { assert.strictEqual(myp5.mouseButton, 0); }); - test('mouseButton should be "left" on left mouse button click', function() { + test('mouseButton should be "left" on left mouse button click', function () { window.dispatchEvent(new PointerEvent('pointerdown', { button: 0 })); assert.strictEqual(myp5.mouseButton, 'left'); }); - test('mouseButton should be "center" on auxillary mouse button click', function() { + test('mouseButton should be "center" on auxillary mouse button click', function () { window.dispatchEvent(new PointerEvent('pointerdown', { button: 1 })); assert.strictEqual(myp5.mouseButton, 'center'); }); - test('mouseButton should be "right" on right mouse button click', function() { + test('mouseButton should be "right" on right mouse button click', function () { window.dispatchEvent(new PointerEvent('pointerdown', { button: 2 })); assert.strictEqual(myp5.mouseButton, 'right'); }); }); - suite('p5.prototype.mouseIsPressed', function() { - test('mouseIsPressed should be a boolean', function() { + suite('p5.prototype.mouseIsPressed', function () { + test('mouseIsPressed should be a boolean', function () { assert.isBoolean(myp5.mouseIsPressed); }); - test.todo('mouseIsPressed should be false if mouse is not pressed', function() { + test.todo('mouseIsPressed should be false if mouse is not pressed', function () { assert.strictEqual(myp5.mouseIsPressed, false); }); - test('mouseIsPressed should be true if mouse is pressed', function() { + test('mouseIsPressed should be true if mouse is pressed', function () { window.dispatchEvent(new MouseEvent('mousedown')); assert.strictEqual(myp5.mouseIsPressed, true); }); - test('mouseIsPressed should reset to false on blur', function() { + test('mouseIsPressed should reset to false on blur', function () { window.dispatchEvent(new PointerEvent('pointerdown')); assert.strictEqual(myp5.mouseIsPressed, true); window.dispatchEvent(new Event('blur')); @@ -260,11 +266,11 @@ suite.todo('Mouse Events', function() { }); }); - suite('mouseMoved', function() { - test.todo('mouseMoved function must run when mouse is moved', async function() { + suite('mouseMoved', function () { + test.todo('mouseMoved function must run when mouse is moved', async function () { let count = 0; - myp5.mouseMoved = function() { + myp5.mouseMoved = function () { count += 1; }; @@ -273,15 +279,15 @@ suite.todo('Mouse Events', function() { assert.deepEqual(count, 2); }); - test('mouseMoved functions on multiple instances must run once', async function() { - let sketchFn = function(sketch, resolve, reject) { + test('mouseMoved functions on multiple instances must run once', async function () { + let sketchFn = function (sketch, resolve, reject) { let count = 0; - sketch.mouseMoved = function() { + sketch.mouseMoved = function () { count += 1; }; - sketch.finish = function() { + sketch.finish = function () { resolve(count); }; }; @@ -294,11 +300,11 @@ suite.todo('Mouse Events', function() { }); }); - suite('mouseDragged', function() { - test('mouseDragged function must run when mouse is dragged', async function() { + suite('mouseDragged', function () { + test('mouseDragged function must run when mouse is dragged', async function () { let count = 0; - myp5.mouseDragged = function() { + myp5.mouseDragged = function () { count += 1; }; @@ -307,15 +313,15 @@ suite.todo('Mouse Events', function() { assert.deepEqual(count, 1); }); - test('mouseDragged functions on multiple instances must run once', async function() { - let sketchFn = function(sketch, resolve, reject) { + test('mouseDragged functions on multiple instances must run once', async function () { + let sketchFn = function (sketch, resolve, reject) { let count = 0; - sketch.mouseDragged = function() { + sketch.mouseDragged = function () { count += 1; }; - sketch.finish = function() { + sketch.finish = function () { resolve(count); }; }; @@ -329,11 +335,11 @@ suite.todo('Mouse Events', function() { }); }); - suite('mousePressed', function() { - test('mousePressed function must run when mouse is pressed', async function() { + suite('mousePressed', function () { + test('mousePressed function must run when mouse is pressed', async function () { let count = 0; - myp5.mousePressed = function() { + myp5.mousePressed = function () { count += 1; }; @@ -341,15 +347,15 @@ suite.todo('Mouse Events', function() { assert.deepEqual(count, 1); }); - test('mousePressed functions on multiple instances must run once', async function() { - let sketchFn = function(sketch, resolve, reject) { + test('mousePressed functions on multiple instances must run once', async function () { + let sketchFn = function (sketch, resolve, reject) { let count = 0; - sketch.mousePressed = function() { + sketch.mousePressed = function () { count += 1; }; - sketch.finish = function() { + sketch.finish = function () { resolve(count); }; }; @@ -362,11 +368,11 @@ suite.todo('Mouse Events', function() { }); }); - suite('mouseReleased', function() { - test('mouseReleased function must run when mouse is released', async function() { + suite('mouseReleased', function () { + test('mouseReleased function must run when mouse is released', async function () { let count = 0; - myp5.mouseReleased = function() { + myp5.mouseReleased = function () { count += 1; }; @@ -374,15 +380,15 @@ suite.todo('Mouse Events', function() { assert.deepEqual(count, 1); }); - test('mouseReleased functions on multiple instances must run once', async function() { - let sketchFn = function(sketch, resolve, reject) { + test('mouseReleased functions on multiple instances must run once', async function () { + let sketchFn = function (sketch, resolve, reject) { let count = 0; - sketch.mouseReleased = function() { + sketch.mouseReleased = function () { count += 1; }; - sketch.finish = function() { + sketch.finish = function () { resolve(count); }; }; @@ -395,11 +401,11 @@ suite.todo('Mouse Events', function() { }); }); - suite('mouseClicked', function() { - test('mouseClicked function must run when mouse is clicked', async function() { + suite('mouseClicked', function () { + test('mouseClicked function must run when mouse is clicked', async function () { let count = 0; - myp5.mouseClicked = function() { + myp5.mouseClicked = function () { count += 1; }; @@ -407,15 +413,15 @@ suite.todo('Mouse Events', function() { assert.deepEqual(count, 1); }); - test('mouseClicked functions on multiple instances must run once', async function() { - let sketchFn = function(sketch, resolve, reject) { + test('mouseClicked functions on multiple instances must run once', async function () { + let sketchFn = function (sketch, resolve, reject) { let count = 0; - sketch.mouseClicked = function() { + sketch.mouseClicked = function () { count += 1; }; - sketch.finish = function() { + sketch.finish = function () { resolve(count); }; }; @@ -428,11 +434,11 @@ suite.todo('Mouse Events', function() { }); }); - suite('doubleClicked', function() { - test('doubleClicked function must run when mouse is double clicked', async function() { + suite('doubleClicked', function () { + test('doubleClicked function must run when mouse is double clicked', async function () { let count = 0; - myp5.doubleClicked = function() { + myp5.doubleClicked = function () { count += 1; }; @@ -440,15 +446,15 @@ suite.todo('Mouse Events', function() { assert.deepEqual(count, 1); }); - test('doubleClicked functions on multiple instances must run once', async function() { - let sketchFn = function(sketch, resolve, reject) { + test('doubleClicked functions on multiple instances must run once', async function () { + let sketchFn = function (sketch, resolve, reject) { let count = 0; - sketch.doubleClicked = function() { + sketch.doubleClicked = function () { count += 1; }; - sketch.finish = function() { + sketch.finish = function () { resolve(count); }; }; @@ -461,11 +467,11 @@ suite.todo('Mouse Events', function() { }); }); - suite('mouseWheel', function() { - test('mouseWheel function must run when mouse wheel event is detected', async function() { + suite('mouseWheel', function () { + test('mouseWheel function must run when mouse wheel event is detected', async function () { let count = 0; - myp5.mouseWheel = function() { + myp5.mouseWheel = function () { count += 1; }; @@ -473,15 +479,15 @@ suite.todo('Mouse Events', function() { assert.deepEqual(count, 1); }); - test('mouseWheel functions on multiple instances must run once', async function() { - let sketchFn = function(sketch, resolve, reject) { + test('mouseWheel functions on multiple instances must run once', async function () { + let sketchFn = function (sketch, resolve, reject) { let count = 0; - sketch.mouseWheel = function() { + sketch.mouseWheel = function () { count += 1; }; - sketch.finish = function() { + sketch.finish = function () { resolve(count); }; }; diff --git a/test/unit/events/touch.js b/test/unit/events/touch.js index f1091e5b6e..64528d7178 100644 --- a/test/unit/events/touch.js +++ b/test/unit/events/touch.js @@ -1,14 +1,14 @@ import p5 from '../../../src/app.js'; -suite('Touch Events', function() { +suite('Touch Events', function () { let myp5; let touchEvent1; let touchEvent2; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; touchEvent1 = new PointerEvent('pointerdown', { pointerId: 1, @@ -28,36 +28,36 @@ suite('Touch Events', function() { }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - beforeEach(function() { + beforeEach(function () { // Reset pointer state so tests don't leak active pointers into each other. myp5._activePointers.clear(); myp5.touches = []; myp5.mouseIsPressed = false; }); - suite('p5.prototype.touches', function() { - test('should be an empty array', function() { + suite('p5.prototype.touches', function () { + test('should be an empty array', function () { assert.deepEqual(myp5.touches, []); }); - test('should be an array of multiple touches', function() { + test('should be an array of multiple touches', function () { window.dispatchEvent(touchEvent1); window.dispatchEvent(touchEvent2); assert.strictEqual(myp5.touches.length, 2); }); - test('should contain the touch registered', function() { + test('should contain the touch registered', function () { window.dispatchEvent(touchEvent1); assert.strictEqual(myp5.touches[0].id, 1); }); }); - suite('p5.prototype._onpointercancel', function() { - test('should remove the cancelled touch from touches', function() { + suite('p5.prototype._onpointercancel', function () { + test('should remove the cancelled touch from touches', function () { window.dispatchEvent(touchEvent1); window.dispatchEvent(touchEvent2); assert.strictEqual(myp5.touches.length, 2); @@ -76,7 +76,7 @@ suite('Touch Events', function() { assert.strictEqual(myp5.touches[0].id, 2); }); - test('should reset mouseIsPressed once all pointers are cancelled', function() { + test('should reset mouseIsPressed once all pointers are cancelled', function () { window.dispatchEvent(touchEvent1); assert.strictEqual(myp5.mouseIsPressed, true); diff --git a/test/unit/image/downloading.js b/test/unit/image/downloading.js index 3d37d979e3..d9956bc68e 100644 --- a/test/unit/image/downloading.js +++ b/test/unit/image/downloading.js @@ -12,9 +12,9 @@ const mockAnchorElement = vi.mockObject({ }); const originalCreateElement = document.createElement; vi.spyOn(document, 'createElement').mockImplementation((...args) => { - if(args[0] !== 'a'){ + if (args[0] !== 'a') { return originalCreateElement.apply(document, args); - }else{ + } else { return mockAnchorElement; } }); @@ -66,7 +66,7 @@ const wait = async time => { }; suite('Downloading', () => { - beforeAll(async function() { + beforeAll(async function () { image(mockP5, mockP5Prototype); files(mockP5, mockP5Prototype); loading(mockP5, mockP5Prototype); @@ -77,21 +77,21 @@ suite('Downloading', () => { vi.clearAllMocks(); }); - suite('downloading animated gifs', function() { + suite('downloading animated gifs', function () { let myGif; const imagePath = '/test/unit/assets/nyan_cat.gif'; - beforeAll(async function() { + beforeAll(async function () { myGif = await mockP5Prototype.loadImage(imagePath); }); - suite('p5.prototype.encodeAndDownloadGif', function() { - test('should be a function', function() { + suite('p5.prototype.encodeAndDownloadGif', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.encodeAndDownloadGif); assert.typeOf(mockP5Prototype.encodeAndDownloadGif, 'function'); }); - test('should not throw an error', function() { + test('should not throw an error', function () { mockP5Prototype.encodeAndDownloadGif(myGif); }); @@ -105,8 +105,8 @@ suite('Downloading', () => { }); }); - suite('p5.prototype.saveCanvas', function() { - test('should be a function', function() { + suite('p5.prototype.saveCanvas', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.saveCanvas); assert.typeOf(mockP5Prototype.saveCanvas, 'function'); }); @@ -136,8 +136,8 @@ suite('Downloading', () => { }); }); - suite('p5.prototype.saveFrames', function() { - test('should be a function', function() { + suite('p5.prototype.saveFrames', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.saveFrames); assert.typeOf(mockP5Prototype.saveFrames, 'function'); }); @@ -171,8 +171,8 @@ suite('Downloading', () => { }); }); - suite('p5.prototype.saveGif', function() { - test('should be a function', function() { + suite('p5.prototype.saveGif', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.saveGif); assert.typeOf(mockP5Prototype.saveGif, 'function'); }); @@ -183,7 +183,10 @@ suite('Downloading', () => { }); test.todo('should not throw an error', async () => { - await mockP5Prototype.saveGif('myGif', 3, { delay: 2, frames: 'seconds' }); + await mockP5Prototype.saveGif('myGif', 3, { + delay: 2, + frames: 'seconds' + }); }); test.todo('should download a GIF', async () => { diff --git a/test/unit/image/filters.js b/test/unit/image/filters.js index 9859b220e7..a8c4ac0f8c 100644 --- a/test/unit/image/filters.js +++ b/test/unit/image/filters.js @@ -1,12 +1,12 @@ import p5 from '../../../src/app.js'; -suite.todo('Filters', function() { +suite.todo('Filters', function () { var myp5; let img; - beforeEach(function() { - new p5(function(p) { - p.setup = function() { + beforeEach(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; myp5.createCanvas(10, 10); img = myp5.createImage(10, 10); @@ -22,11 +22,11 @@ suite.todo('Filters', function() { }); }); - afterEach(function() { + afterEach(function () { myp5.remove(); }); - test('threshold filter. less than threshold', function() { + test('threshold filter. less than threshold', function () { img.filter(myp5.THRESHOLD); myp5.image(img, 0, 0); myp5.loadPixels(); @@ -37,7 +37,7 @@ suite.todo('Filters', function() { } }); - test('threshold filter. greater than threshold', function() { + test('threshold filter. greater than threshold', function () { img.loadPixels(); for (let i = 0; i < img.width; i++) { for (let j = 0; j < img.height; j++) { @@ -55,7 +55,7 @@ suite.todo('Filters', function() { } }); - test('gray filter', function() { + test('gray filter', function () { img.filter(myp5.GRAY); myp5.image(img, 0, 0); myp5.loadPixels(); @@ -65,7 +65,7 @@ suite.todo('Filters', function() { } }); - test('opaque filter', function() { + test('opaque filter', function () { img.loadPixels(); for (let i = 0; i < img.width; i++) { for (let j = 0; j < img.height; j++) { @@ -81,7 +81,7 @@ suite.todo('Filters', function() { } }); - test('invert filter', function() { + test('invert filter', function () { img.filter(myp5.INVERT); myp5.image(img, 0, 0); myp5.loadPixels(); diff --git a/test/unit/image/loading.js b/test/unit/image/loading.js index 94c4c02b77..4b53cbb469 100644 --- a/test/unit/image/loading.js +++ b/test/unit/image/loading.js @@ -9,16 +9,16 @@ import { vi } from 'vitest'; * Expects an image file and a p5 instance with an image file loaded and drawn * and checks that they are exactly the same. Sends result to the callback. */ -var testImageRender = function(file, sketch) { +var testImageRender = function (file, sketch) { sketch.loadPixels(); var p = sketch.pixels; var ctx = sketch; sketch.clear(); - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { sketch.loadImage(file, resolve, reject); - }).then(function(img) { + }).then(function (img) { ctx.image(img, 0, 0); ctx.loadPixels(); @@ -32,7 +32,7 @@ var testImageRender = function(file, sketch) { }); }; -suite('loading images', function() { +suite('loading images', function () { const imagePath = '/test/unit/assets/cat.jpg'; const singleFrameGif = '/test/unit/assets/target_small.gif'; const animatedGif = '/test/unit/assets/white_black.gif'; @@ -43,37 +43,45 @@ suite('loading images', function() { const svgImage = '/test/unit/assets/green.svg'; const invalidFile = '404file'; - beforeAll(async function() { + beforeAll(async function () { loadingDisplaying(mockP5, mockP5Prototype); image(mockP5, mockP5Prototype); await httpMock.start({ quiet: true }); }); test('throws error when encountering HTTP errors', async () => { - await expect(mockP5Prototype.loadImage(invalidFile)) - .rejects - .toThrow('Not Found'); + await expect(mockP5Prototype.loadImage(invalidFile)).rejects.toThrow( + 'Not Found' + ); }); test('error callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadImage(invalidFile, () => { - reject('Success callback executed'); - }, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }); + mockP5Prototype.loadImage( + invalidFile, + () => { + reject('Success callback executed'); + }, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + } + ); }); }); test('success callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadImage(imagePath, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }, err => { - reject(`Error callback called: ${err.toString()}`); - }); + mockP5Prototype.loadImage( + imagePath, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + }, + err => { + reject(`Error callback called: ${err.toString()}`); + } + ); }); }); @@ -265,7 +273,9 @@ suite('loading images', function() { // errored load const INVALID_PATH = ''; - try { await mockP5Prototype.loadImage(INVALID_PATH); } catch {} + try { + await mockP5Prototype.loadImage(INVALID_PATH); + } catch {} expect(revokeSpy).toHaveBeenCalledTimes(createSpy.mock.calls.length); createSpy.mockRestore(); @@ -273,26 +283,26 @@ suite('loading images', function() { }); }); -suite.todo('displaying images', function() { +suite.todo('displaying images', function () { var myp5; var pImg; var imagePath = 'unit/assets/cat-with-hole.png'; var chanNames = ['red', 'green', 'blue', 'alpha']; - beforeAll(async function() { + beforeAll(async function () { new Promise(resolve => { - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { myp5 = p; myp5.pixelDensity(1); myp5.loadImage( imagePath, - function(img) { + function (img) { pImg = img; myp5.resizeCanvas(pImg.width, pImg.height); resolve(); }, - function() { + function () { throw new Error('Error loading image'); } ); @@ -301,7 +311,7 @@ suite.todo('displaying images', function() { }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); @@ -344,7 +354,7 @@ suite.todo('displaying images', function() { } } - test('tint() with color', function() { + test('tint() with color', function () { assert.ok(pImg, 'image loaded'); var tintColor = [150, 100, 50, 255]; myp5.clear(); @@ -355,7 +365,7 @@ suite.todo('displaying images', function() { checkTint(tintColor); }); - test('tint() with alpha', function() { + test('tint() with alpha', function () { assert.ok(pImg, 'image loaded'); var tintColor = [255, 255, 255, 100]; myp5.clear(); @@ -366,7 +376,7 @@ suite.todo('displaying images', function() { checkTint(tintColor); }); - test('tint() with color and alpha', function() { + test('tint() with color and alpha', function () { assert.ok(pImg, 'image loaded'); var tintColor = [255, 100, 50, 100]; myp5.clear(); @@ -378,18 +388,18 @@ suite.todo('displaying images', function() { }); }); -suite('displaying images that use fit mode', function() { +suite('displaying images that use fit mode', function () { var myp5, imageSpy; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); @@ -401,7 +411,7 @@ suite('displaying images that use fit mode', function() { vi.restoreAllMocks(); }); - test('CONTAIN when source image is larger than destination', function() { + test('CONTAIN when source image is larger than destination', function () { let src = myp5.createImage(400, 1000); myp5.image(src, 0, 0, 300, 400, 0, 0, 400, 1000, myp5.CONTAIN); expect(imageSpy) @@ -419,7 +429,7 @@ suite('displaying images that use fit mode', function() { ); }); - test('CONTAIN when source image is smaller than destination', function() { + test('CONTAIN when source image is smaller than destination', function () { let src = myp5.createImage(40, 90); myp5.image(src, 0, 0, 300, 500, 0, 0, 400, 1000, myp5.CONTAIN); expect(imageSpy) @@ -437,7 +447,7 @@ suite('displaying images that use fit mode', function() { ); }); - test('COVER when source image is larger than destination', function() { + test('COVER when source image is larger than destination', function () { let src = myp5.createImage(400, 1000); myp5.image(src, 0, 0, 300, 400, 0, 0, 400, 1000, myp5.COVER); const r = Math.max(300 / 400, 400 / 1000); @@ -456,7 +466,7 @@ suite('displaying images that use fit mode', function() { ); }); - test('COVER when source image is smaller than destination', function() { + test('COVER when source image is smaller than destination', function () { let src = myp5.createImage(20, 100); myp5.image(src, 0, 0, 300, 400, 0, 0, 20, 100, myp5.COVER); const r = Math.max(300 / 20, 400 / 100); diff --git a/test/unit/image/p5.Image.js b/test/unit/image/p5.Image.js index 5195f2651d..9962a2dfe6 100644 --- a/test/unit/image/p5.Image.js +++ b/test/unit/image/p5.Image.js @@ -1,29 +1,29 @@ import p5 from '../../../src/app.js'; -suite('p5.Image', function() { +suite('p5.Image', function () { var myp5; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('p5.prototype.createImage', function() { - test('it creates an image', function() { + suite('p5.prototype.createImage', function () { + test('it creates an image', function () { let img = myp5.createImage(10, 17); assert.isObject(img); }); }); - suite('p5.Image', function() { - test('it has necessary properties', function() { + suite('p5.Image', function () { + test('it has necessary properties', function () { let img = new p5.Image(100, 100); assert.property(img, 'width'); assert.property(img, 'height'); @@ -33,7 +33,7 @@ suite('p5.Image', function() { assert.property(img, 'updatePixels'); }); - test('height and width are correct', function() { + test('height and width are correct', function () { let img = new p5.Image(100, 100); myp5.pixelDensity(1); assert.strictEqual(img.width, 100); @@ -41,8 +41,8 @@ suite('p5.Image', function() { }); }); - suite('p5.Image.prototype.resize', function() { - test('it should resize the image', function() { + suite('p5.Image.prototype.resize', function () { + test('it should resize the image', function () { let img = myp5.createImage(10, 17); myp5.pixelDensity(1); img.resize(10, 30); @@ -51,9 +51,9 @@ suite('p5.Image', function() { }); }); - suite.todo('p5.Image.prototype.mask', function() { + suite.todo('p5.Image.prototype.mask', function () { for (const density of [1, 2]) { - test(`it should mask the image at pixel density ${density}`, function() { + test(`it should mask the image at pixel density ${density}`, function () { let img = myp5.createImage(10, 10); img.pixelDensity(density); img.loadPixels(); @@ -87,7 +87,7 @@ suite('p5.Image', function() { }); } - test('it should mask images of different density', function() { + test('it should mask images of different density', function () { let img = myp5.createImage(10, 10); img.pixelDensity(1); img.loadPixels(); @@ -120,15 +120,15 @@ suite('p5.Image', function() { } }); - test('it should mask images from createGraphics', function() { - myp5.createCanvas(10,10); + test('it should mask images from createGraphics', function () { + myp5.createCanvas(10, 10); myp5.pixelDensity(2); - let img = myp5.createGraphics(10,10); + let img = myp5.createGraphics(10, 10); img.noStroke(); - img.rect(0,0,10,10); - let mask = myp5.createGraphics(10,10); + img.rect(0, 0, 10, 10); + let mask = myp5.createGraphics(10, 10); mask.noStroke(); - mask.rect(0,0,5,5); + mask.rect(0, 0, 5, 5); let masked = img.get(); masked.mask(mask.get()); @@ -140,11 +140,11 @@ suite('p5.Image', function() { } }); - test('it should mask the animated gif image', function() { + test('it should mask the animated gif image', function () { const imagePath = 'unit/assets/nyan_cat.gif'; - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { myp5.loadImage(imagePath, resolve, reject); - }).then(function(img) { + }).then(function (img) { let mask = myp5.createImage(img.width, img.height); mask.loadPixels(); for (let i = 0; i < mask.width; i++) { @@ -181,9 +181,9 @@ suite('p5.Image', function() { }); }); - suite.todo('p5.Graphics.get()', function() { + suite.todo('p5.Graphics.get()', function () { for (const density of [1, 2]) { - test(`width and height match at pixel density ${density}`, function() { + test(`width and height match at pixel density ${density}`, function () { const g = myp5.createGraphics(10, 10); g.pixelDensity(density); g.rect(2, 2, 5, 5); diff --git a/test/unit/image/pixels.js b/test/unit/image/pixels.js index e0b4958c71..f6f970418f 100644 --- a/test/unit/image/pixels.js +++ b/test/unit/image/pixels.js @@ -1,24 +1,24 @@ import p5 from '../../../src/app.js'; -suite.todo('pixels', function() { +suite.todo('pixels', function () { var myp5; - beforeAll(function() { - new p5(function(p) { - p.setup = function() { + beforeAll(function () { + new p5(function (p) { + p.setup = function () { myp5 = p; }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('p5.Image.get', function() { + suite('p5.Image.get', function () { var img; - beforeAll(function() { + beforeAll(function () { //create a 50 x 50 half red half green image img = myp5.createImage(50, 50); img.loadPixels(); @@ -40,21 +40,21 @@ suite.todo('pixels', function() { img.updatePixels(); }); - test('get(x,y) works with integers', function() { + test('get(x,y) works with integers', function () { assert.deepEqual(img.get(25, 25), [255, 0, 0, 255]); assert.deepEqual(img.get(25, 26), [0, 0, 255, 255]); assert.deepEqual(img.get(0, 0), [255, 0, 0, 255]); assert.deepEqual(img.get(49, 49), [0, 0, 255, 255]); }); - test('get(x,y) returns 0s for out of bounds arguments', function() { + test('get(x,y) returns 0s for out of bounds arguments', function () { assert.deepEqual(img.get(25, -1), [0, 0, 0, 0]); assert.deepEqual(img.get(-1, 26), [0, 0, 0, 0]); assert.deepEqual(img.get(25, 50), [0, 0, 0, 0]); assert.deepEqual(img.get(50, 26), [0, 0, 0, 0]); }); - test('get() returns a copy when no arguments are supplied', function() { + test('get() returns a copy when no arguments are supplied', function () { var copy = img.get(); assert.instanceOf(copy, p5.Image); assert.equal(copy.width, img.width); @@ -66,7 +66,7 @@ suite.todo('pixels', function() { assert.deepEqual(copy.get(49, 49), [0, 0, 255, 255]); }); - test('get(x,y,w,h) works', function() { + test('get(x,y,w,h) works', function () { for (var w = 1; w < img.width + 5; w += 2) { for (var x = -w * 2; x <= img.width + w * 2; x += 4) { var copy = img.get(x, x, w, w); @@ -78,26 +78,26 @@ suite.todo('pixels', function() { } }); - test('rounds down when given decimal numbers', function() { + test('rounds down when given decimal numbers', function () { assert.deepEqual(img.get(25, 25.999), img.get(25, 25)); }); }); - suite('p5.Image.pixels', function() { - test('should be an array of pixels', function() { + suite('p5.Image.pixels', function () { + test('should be an array of pixels', function () { let img = myp5.createImage(10, 10); img.loadPixels(); assert.typeOf(img.pixels, 'Uint8ClampedArray'); }); - test('should store r, g, b, a values for each pixel', function() { + test('should store r, g, b, a values for each pixel', function () { let img = myp5.createImage(10, 10); myp5.pixelDensity(1); img.loadPixels(); assert.strictEqual(img.pixels.length, 400); }); - test('should store correct r, g, b, a values for each pixel', function() { + test('should store correct r, g, b, a values for each pixel', function () { let img = myp5.createImage(10, 10); myp5.pixelDensity(1); img.loadPixels(); @@ -114,9 +114,9 @@ suite.todo('pixels', function() { }); }); - suite('p5.Image.set', function() { + suite('p5.Image.set', function () { /* Parameter Validation missing */ - test('set(x,y) changes color of pixel (x, y)', function() { + test('set(x,y) changes color of pixel (x, y)', function () { let img = myp5.createImage(50, 50); img.loadPixels(); img.set(0, 0, myp5.color(255, 34, 19)); @@ -125,8 +125,8 @@ suite.todo('pixels', function() { }); }); - suite('p5.Image.blend', function() { - test('should copy a region of pixels using the specified blend mode', function() { + suite('p5.Image.blend', function () { + test('should copy a region of pixels using the specified blend mode', function () { let img = myp5.createImage(50, 50); let img2 = myp5.createImage(50, 50); img.loadPixels(); @@ -154,8 +154,8 @@ suite.todo('pixels', function() { }); }); - suite('p5.Image.copy', function() { - test('should copy a region of pixels', function() { + suite('p5.Image.copy', function () { + test('should copy a region of pixels', function () { let img = myp5.createImage(50, 50); let img2 = myp5.createImage(50, 50); img.loadPixels(); diff --git a/test/unit/io/files.js b/test/unit/io/files.js index cad7983690..1c30776b8c 100644 --- a/test/unit/io/files.js +++ b/test/unit/io/files.js @@ -9,16 +9,16 @@ const mockAnchorElement = vi.mockObject({ }); const originalCreateElement = document.createElement; vi.spyOn(document, 'createElement').mockImplementation((...args) => { - if(args[0] !== 'a'){ + if (args[0] !== 'a') { return originalCreateElement.apply(document, args); - }else{ + } else { return mockAnchorElement; } }); vi.spyOn(URL, 'createObjectURL'); -suite('Files', function() { - beforeAll(async function() { +suite('Files', function () { + beforeAll(async function () { files(mockP5, mockP5Prototype); await httpMock.start({ quiet: true }); }); @@ -28,35 +28,44 @@ suite('Files', function() { }); // httpDo - suite('httpDo()', function() { - test('should work when provided with just a path', async function() { - const data = await mockP5Prototype.httpDo('/test/unit/assets/sentences.txt'); + suite('httpDo()', function () { + test('should work when provided with just a path', async function () { + const data = await mockP5Prototype.httpDo( + '/test/unit/assets/sentences.txt' + ); assert.ok(data); assert.isString(data); }); - test('should accept method parameter', async function() { - const data = await mockP5Prototype.httpDo('/test/unit/assets/sentences.txt', 'GET'); + test('should accept method parameter', async function () { + const data = await mockP5Prototype.httpDo( + '/test/unit/assets/sentences.txt', + 'GET' + ); assert.ok(data); assert.isString(data); }); - test('should accept method and type parameter together', async function() { - const data = await mockP5Prototype.httpDo('/test/unit/assets/sentences.txt', 'GET', 'text'); + test('should accept method and type parameter together', async function () { + const data = await mockP5Prototype.httpDo( + '/test/unit/assets/sentences.txt', + 'GET', + 'text' + ); assert.ok(data); assert.isString(data); }); - test('should handle promise error correctly', async function() { - await expect(mockP5Prototype.httpDo('/test/unit/assets/sen.txt')) - .rejects - .toThrow('Not Found'); + test('should handle promise error correctly', async function () { + await expect( + mockP5Prototype.httpDo('/test/unit/assets/sen.txt') + ).rejects.toThrow('Not Found'); }); }); // saveStrings() - suite('p5.prototype.saveStrings', function() { - test('should be a function', function() { + suite('p5.prototype.saveStrings', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.saveStrings); assert.typeOf(mockP5Prototype.saveStrings, 'function'); }); @@ -85,8 +94,8 @@ suite('Files', function() { }); // saveJSON() - suite('p5.prototype.saveJSON', function() { - test('should be a function', function() { + suite('p5.prototype.saveJSON', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.saveJSON); assert.typeOf(mockP5Prototype.saveJSON, 'function'); }); @@ -104,8 +113,8 @@ suite('Files', function() { }); // writeFile() - suite('p5.prototype.writeFile', function() { - test('should be a function', function() { + suite('p5.prototype.writeFile', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.writeFile); assert.typeOf(mockP5Prototype.writeFile, 'function'); }); @@ -123,8 +132,8 @@ suite('Files', function() { }); // downloadFile() - suite('p5.prototype.downloadFile', function() { - test('should be a function', function() { + suite('p5.prototype.downloadFile', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.downloadFile); assert.typeOf(mockP5Prototype.downloadFile, 'function'); }); @@ -143,9 +152,9 @@ suite('Files', function() { }); // save() - suite('p5.prototype.save', function() { - suite('saving images', function() { - test('should be a function', function() { + suite('p5.prototype.save', function () { + suite('saving images', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.save); assert.typeOf(mockP5Prototype.save, 'function'); }); @@ -155,19 +164,22 @@ suite('Files', function() { test('should call saveCanvas', async () => { mockP5Prototype.save(); expect(mockP5Prototype.saveCanvas).toHaveBeenCalledTimes(1); - expect(mockP5Prototype.saveCanvas) - .toHaveBeenCalledWith(mockP5Prototype.elt); + expect(mockP5Prototype.saveCanvas).toHaveBeenCalledWith( + mockP5Prototype.elt + ); }); test('should call saveCanvas with filename', async () => { mockP5Prototype.save('filename.jpg'); expect(mockP5Prototype.saveCanvas).toHaveBeenCalledTimes(1); - expect(mockP5Prototype.saveCanvas) - .toHaveBeenCalledWith(mockP5Prototype.elt, 'filename.jpg'); + expect(mockP5Prototype.saveCanvas).toHaveBeenCalledWith( + mockP5Prototype.elt, + 'filename.jpg' + ); }); }); - suite('saving strings and json', function() { + suite('saving strings and json', function () { test('should download a text file', async () => { const myStrings = ['aaa', 'bbb']; mockP5Prototype.save(myStrings, 'filename'); diff --git a/test/unit/io/loadBytes.js b/test/unit/io/loadBytes.js index e5e95b464c..bfa62955d8 100644 --- a/test/unit/io/loadBytes.js +++ b/test/unit/io/loadBytes.js @@ -1,7 +1,7 @@ import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks'; import files from '../../../src/io/files'; -suite('loadBytes', function() { +suite('loadBytes', function () { const invalidFile = '404file'; const validFile = '/test/unit/assets/nyan_cat.gif'; @@ -11,41 +11,49 @@ suite('loadBytes', function() { }); test('throws error when encountering HTTP errors', async () => { - await expect(mockP5Prototype.loadBytes(invalidFile)) - .rejects - .toThrow('Not Found'); + await expect(mockP5Prototype.loadBytes(invalidFile)).rejects.toThrow( + 'Not Found' + ); }); test('error callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadBytes(invalidFile, () => { - reject('Success callback executed'); - }, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }); + mockP5Prototype.loadBytes( + invalidFile, + () => { + reject('Success callback executed'); + }, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + } + ); }); }); test('success callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadBytes(validFile, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }, err => { - reject(`Error callback called: ${err.toString()}`); - }); + mockP5Prototype.loadBytes( + validFile, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + }, + err => { + reject(`Error callback called: ${err.toString()}`); + } + ); }); }); - test('returns the correct object', async () => { + test('returns the correct object', async () => { const data = await mockP5Prototype.loadBytes(validFile); assert.instanceOf(data, Uint8Array); // Validate data const str = 'GIF89a'; // convert the string to a byte array - const rgb = str.split('').map(function(e) { + const rgb = str.split('').map(function (e) { return e.charCodeAt(0); }); // this will convert a Uint8Aray to [], if necessary: @@ -53,14 +61,14 @@ suite('loadBytes', function() { assert.deepEqual(loaded, rgb); }); - test('passes athe correct object to success callback', async () => { + test('passes athe correct object to success callback', async () => { await mockP5Prototype.loadBytes(validFile, data => { assert.instanceOf(data, Uint8Array); // Validate data const str = 'GIF89a'; // convert the string to a byte array - const rgb = str.split('').map(function(e) { + const rgb = str.split('').map(function (e) { return e.charCodeAt(0); }); // this will convert a Uint8Aray to [], if necessary: diff --git a/test/unit/io/loadJSON.js b/test/unit/io/loadJSON.js index ea57d28d33..f0e5b622ef 100644 --- a/test/unit/io/loadJSON.js +++ b/test/unit/io/loadJSON.js @@ -1,7 +1,7 @@ import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks'; import files from '../../../src/io/files'; -suite('loadJSON', function() { +suite('loadJSON', function () { const invalidFile = '404file'; const jsonArrayFile = '/test/unit/assets/array.json'; const jsonObjectFile = '/test/unit/assets/object.json'; @@ -12,30 +12,38 @@ suite('loadJSON', function() { }); test('throws error when encountering HTTP errors', async () => { - await expect(mockP5Prototype.loadJSON(invalidFile)) - .rejects - .toThrow('Not Found'); + await expect(mockP5Prototype.loadJSON(invalidFile)).rejects.toThrow( + 'Not Found' + ); }); test('error callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadJSON(invalidFile, () => { - reject('Success callback executed'); - }, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }); + mockP5Prototype.loadJSON( + invalidFile, + () => { + reject('Success callback executed'); + }, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + } + ); }); }); test('success callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadJSON(jsonObjectFile, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }, err => { - reject(`Error callback called: ${err.toString()}`); - }); + mockP5Prototype.loadJSON( + jsonObjectFile, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + }, + err => { + reject(`Error callback called: ${err.toString()}`); + } + ); }); }); @@ -57,7 +65,7 @@ suite('loadJSON', function() { assert.lengthOf(data, 3); }); - test('passes an array to success callback for array JSON.', async function() { + test('passes an array to success callback for array JSON.', async function () { await mockP5Prototype.loadJSON(jsonArrayFile, data => { assert.isArray(data); assert.lengthOf(data, 3); diff --git a/test/unit/io/loadModel.js b/test/unit/io/loadModel.js index 590a12bb1e..587323fb84 100644 --- a/test/unit/io/loadModel.js +++ b/test/unit/io/loadModel.js @@ -2,7 +2,7 @@ import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks'; import loading from '../../../src/webgl/loading'; import { Geometry } from '../../../src/webgl/p5.Geometry'; -suite('loadModel', function() { +suite('loadModel', function () { const invalidFile = '404file'; const validFile = '/test/unit/assets/teapot.obj'; const validObjFileforMtl = '/test/unit/assets/octa-color.obj'; @@ -19,67 +19,58 @@ suite('loadModel', function() { }); test('throws error when encountering HTTP errors', async () => { - await expect(mockP5Prototype.loadModel(invalidFile)) - .rejects - .toThrow('Not Found'); + await expect(mockP5Prototype.loadModel(invalidFile)).rejects.toThrow( + 'Not Found' + ); }); test('error callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadModel(invalidFile, () => { - reject('Success callback executed'); - }, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }); + mockP5Prototype.loadModel( + invalidFile, + () => { + reject('Success callback executed'); + }, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + } + ); }); }); test('success callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadModel(validFile, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }, err => { - reject(`Error callback called: ${err.toString()}`); - }); + mockP5Prototype.loadModel( + validFile, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + }, + err => { + reject(`Error callback called: ${err.toString()}`); + } + ); }); }); - test('loads OBJ file with associated MTL file correctly', async function(){ + test('loads OBJ file with associated MTL file correctly', async function () { const model = await mockP5Prototype.loadModel(validObjFileforMtl); const expectedColors = [ - 0, 0, 0.5, 1, - 0, 0, 0.5, 1, - 0, 0, 0.5, 1, - 0, 0, 0.942654, 1, - 0, 0, 0.942654, 1, - 0, 0, 0.942654, 1, - 0, 0.815632, 1, 1, - 0, 0.815632, 1, 1, - 0, 0.815632, 1, 1, - 0, 0.965177, 1, 1, - 0, 0.965177, 1, 1, - 0, 0.965177, 1, 1, - 0.848654, 1, 0.151346, 1, - 0.848654, 1, 0.151346, 1, - 0.848654, 1, 0.151346, 1, - 1, 0.888635, 0, 1, - 1, 0.888635, 0, 1, - 1, 0.888635, 0, 1, - 1, 0.77791, 0, 1, - 1, 0.77791, 0, 1, - 1, 0.77791, 0, 1, - 0.5, 0, 0, 1, - 0.5, 0, 0, 1, - 0.5, 0, 0, 1 + 0, 0, 0.5, 1, 0, 0, 0.5, 1, 0, 0, 0.5, 1, 0, 0, 0.942654, 1, 0, 0, + 0.942654, 1, 0, 0, 0.942654, 1, 0, 0.815632, 1, 1, 0, 0.815632, 1, 1, 0, + 0.815632, 1, 1, 0, 0.965177, 1, 1, 0, 0.965177, 1, 1, 0, 0.965177, 1, 1, + 0.848654, 1, 0.151346, 1, 0.848654, 1, 0.151346, 1, 0.848654, 1, 0.151346, + 1, 1, 0.888635, 0, 1, 1, 0.888635, 0, 1, 1, 0.888635, 0, 1, 1, 0.77791, 0, + 1, 1, 0.77791, 0, 1, 1, 0.77791, 0, 1, 0.5, 0, 0, 1, 0.5, 0, 0, 1, 0.5, 0, + 0, 1 ]; assert.deepEqual(model.vertexColors, expectedColors); }); - test('mixed material coloring loads model with sentinel colors for uncolored vertices', async function() { + test('mixed material coloring loads model with sentinel colors for uncolored vertices', async function () { const model = await mockP5Prototype.loadModel(inconsistentColorObjFile); assert.instanceOf(model, Geometry); assert.equal( @@ -102,51 +93,69 @@ suite('loadModel', function() { assert.isTrue(hasRealColor, 'Colored vertices should retain their color'); }); - test('missing MTL file shows OBJ model without vertexColors', async function() { + test('missing MTL file shows OBJ model without vertexColors', async function () { const model = await mockP5Prototype.loadModel(objMtlMissing); assert.instanceOf(model, Geometry); - assert.equal(model.vertexColors.length, 0, 'Model should not have vertex colors'); + assert.equal( + model.vertexColors.length, + 0, + 'Model should not have vertex colors' + ); }); - test('returns an object with correct data', async function() { + test('returns an object with correct data', async function () { const model = await mockP5Prototype.loadModel(validFile); assert.instanceOf(model, Geometry); }); - test('passes an object with correct data to callback', async function() { + test('passes an object with correct data to callback', async function () { await mockP5Prototype.loadModel(validFile, model => { assert.instanceOf(model, Geometry); }); }); - test('resolves STL file correctly', async function() { + test('resolves STL file correctly', async function () { const model = await mockP5Prototype.loadModel(validSTLfile); assert.instanceOf(model, Geometry); }); - test('resolves STL file correctly with explicit extension', async function() { - const model = await mockP5Prototype.loadModel(validSTLfileWithoutExtension, '.stl'); + test('resolves STL file correctly with explicit extension', async function () { + const model = await mockP5Prototype.loadModel( + validSTLfileWithoutExtension, + '.stl' + ); assert.instanceOf(model, Geometry); }); - test('resolves STL file correctly with case insensitive extension', async function() { - const model = await mockP5Prototype.loadModel(validSTLfileWithoutExtension, '.STL'); + test('resolves STL file correctly with case insensitive extension', async function () { + const model = await mockP5Prototype.loadModel( + validSTLfileWithoutExtension, + '.STL' + ); assert.instanceOf(model, Geometry); }); - test('OBJ with negative vertex indices loads correctly', async function() { + test('OBJ with negative vertex indices loads correctly', async function () { const model = await mockP5Prototype.loadModel(negativeIndexCubeFile); assert.instanceOf(model, Geometry); assert.isAbove(model.vertices.length, 0, 'Model should have vertices'); assert.isAbove(model.faces.length, 0, 'Model should have faces'); }); - test('OBJ negative indices produce same geometry as positive', async function() { + test('OBJ negative indices produce same geometry as positive', async function () { const positiveModel = await mockP5Prototype.loadModel(validCubeFile); - const negativeModel = await mockP5Prototype.loadModel(negativeIndexCubeFile); - assert.equal(positiveModel.vertices.length, negativeModel.vertices.length, - 'Vertex count should match'); - assert.equal(positiveModel.faces.length, negativeModel.faces.length, - 'Face count should match'); + const negativeModel = await mockP5Prototype.loadModel( + negativeIndexCubeFile + ); + assert.equal( + positiveModel.vertices.length, + negativeModel.vertices.length, + 'Vertex count should match' + ); + assert.equal( + positiveModel.faces.length, + negativeModel.faces.length, + 'Face count should match' + ); }); }); diff --git a/test/unit/io/loadShader.js b/test/unit/io/loadShader.js index 7894f54b39..e2888171d8 100644 --- a/test/unit/io/loadShader.js +++ b/test/unit/io/loadShader.js @@ -2,7 +2,7 @@ import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks'; import material from '../../../src/webgl/material'; import { Shader } from '../../../src/webgl/p5.Shader'; -suite('loadShader', function() { +suite('loadShader', function () { const invalidFile = '404file'; const vertFile = '/test/unit/assets/vert.glsl'; const fragFile = '/test/unit/assets/frag.glsl'; @@ -13,56 +13,71 @@ suite('loadShader', function() { }); test('throws error when encountering HTTP errors in vert shader', async () => { - await expect(mockP5Prototype.loadShader(invalidFile, fragFile)) - .rejects - .toThrow('Not Found'); + await expect( + mockP5Prototype.loadShader(invalidFile, fragFile) + ).rejects.toThrow('Not Found'); }); test('throws error when encountering HTTP errors in frag shader', async () => { - await expect(mockP5Prototype.loadShader(vertFile, invalidFile)) - .rejects - .toThrow('Not Found'); + await expect( + mockP5Prototype.loadShader(vertFile, invalidFile) + ).rejects.toThrow('Not Found'); }); test('error callback is called for vert shader', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadShader(invalidFile, fragFile, () => { - reject('Success callback executed'); - }, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }); + mockP5Prototype.loadShader( + invalidFile, + fragFile, + () => { + reject('Success callback executed'); + }, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + } + ); }); }); test('error callback is called for frag shader', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadShader(vertFile, invalidFile, () => { - reject('Success callback executed'); - }, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }); + mockP5Prototype.loadShader( + vertFile, + invalidFile, + () => { + reject('Success callback executed'); + }, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + } + ); }); }); test('success callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadShader(vertFile, fragFile, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }, err => { - reject(`Error callback called: ${err.toString()}`); - }); + mockP5Prototype.loadShader( + vertFile, + fragFile, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + }, + err => { + reject(`Error callback called: ${err.toString()}`); + } + ); }); }); - test('returns an object with correct data', async function() { + test('returns an object with correct data', async function () { const shader = await mockP5Prototype.loadShader(vertFile, fragFile); assert.instanceOf(shader, Shader); }); - test('passes an object with correct data to callback', async function() { + test('passes an object with correct data to callback', async function () { await mockP5Prototype.loadShader(vertFile, fragFile, shader => { assert.instanceOf(shader, Shader); }); diff --git a/test/unit/io/loadStrings.js b/test/unit/io/loadStrings.js index c8916b97a0..45795b1dfb 100644 --- a/test/unit/io/loadStrings.js +++ b/test/unit/io/loadStrings.js @@ -1,7 +1,7 @@ import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks'; import files from '../../../src/io/files'; -suite('loadStrings', function() { +suite('loadStrings', function () { const invalidFile = '404file'; const validFile = '/test/unit/assets/sentences.txt'; const fileWithEmptyLines = '/test/unit/assets/empty_lines.txt'; @@ -13,57 +13,65 @@ suite('loadStrings', function() { }); test('throws error when encountering HTTP errors', async () => { - await expect(mockP5Prototype.loadStrings(invalidFile)) - .rejects - .toThrow('Not Found'); + await expect(mockP5Prototype.loadStrings(invalidFile)).rejects.toThrow( + 'Not Found' + ); }); test('error callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadStrings(invalidFile, () => { - reject('Success callback executed'); - }, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }); + mockP5Prototype.loadStrings( + invalidFile, + () => { + reject('Success callback executed'); + }, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + } + ); }); }); test('success callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadStrings(validFile, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }, err => { - reject(`Error callback called: ${err.toString()}`); - }); + mockP5Prototype.loadStrings( + validFile, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + }, + err => { + reject(`Error callback called: ${err.toString()}`); + } + ); }); }); - test('returns an array of strings', async () => { + test('returns an array of strings', async () => { const strings = await mockP5Prototype.loadStrings(validFile); assert.isArray(strings); - for(let string of strings){ + for (let string of strings) { assert.isString(string); } }); - test('passes an array to success callback', async () => { + test('passes an array to success callback', async () => { await mockP5Prototype.loadStrings(validFile, strings => { assert.isArray(strings); - for(let string of strings){ + for (let string of strings) { assert.isString(string); } }); }); - test('should include empty strings', async () => { + test('should include empty strings', async () => { const strings = await mockP5Prototype.loadStrings(fileWithEmptyLines); assert.isArray(strings, 'Array passed to callback function'); assert.lengthOf(strings, 6, 'length of data is 6'); }); - test('can load file with many lines', async () => { + test('can load file with many lines', async () => { const strings = await mockP5Prototype.loadStrings(fileWithManyLines); assert.isArray(strings, 'Array passed to callback function'); assert.lengthOf(strings, 131073, 'length of data is 131073'); diff --git a/test/unit/io/loadTable.js b/test/unit/io/loadTable.js index ef22619be6..819e4d4f16 100644 --- a/test/unit/io/loadTable.js +++ b/test/unit/io/loadTable.js @@ -3,7 +3,7 @@ import files from '../../../src/io/files'; import table from '../../../src/io/p5.Table'; import tableRow from '../../../src/io/p5.TableRow'; -suite('loadTable', function() { +suite('loadTable', function () { const invalidFile = '404file'; const validFile = '/test/unit/assets/csv.csv'; @@ -15,30 +15,38 @@ suite('loadTable', function() { }); test('throws error when encountering HTTP errors', async () => { - await expect(mockP5Prototype.loadTable(invalidFile)) - .rejects - .toThrow('Not Found'); + await expect(mockP5Prototype.loadTable(invalidFile)).rejects.toThrow( + 'Not Found' + ); }); test('error callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadTable(invalidFile, () => { - reject('Success callback executed'); - }, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }); + mockP5Prototype.loadTable( + invalidFile, + () => { + reject('Success callback executed'); + }, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + } + ); }); }); test('success callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadTable(validFile, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }, err => { - reject(`Error callback called: ${err.toString()}`); - }); + mockP5Prototype.loadTable( + validFile, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + }, + err => { + reject(`Error callback called: ${err.toString()}`); + } + ); }); }); diff --git a/test/unit/io/loadXML.js b/test/unit/io/loadXML.js index 2715d22d4c..1ee1dbb929 100644 --- a/test/unit/io/loadXML.js +++ b/test/unit/io/loadXML.js @@ -2,7 +2,7 @@ import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks'; import files from '../../../src/io/files'; import xml from '../../../src/io/p5.XML'; -suite('loadXML', function() { +suite('loadXML', function () { const invalidFile = '404file'; const validFile = '/test/unit/assets/books.xml'; @@ -13,31 +13,39 @@ suite('loadXML', function() { }); test('throws error when encountering HTTP errors', async () => { - await expect(mockP5Prototype.loadXML(invalidFile)) - .rejects - .toThrow('Not Found'); + await expect(mockP5Prototype.loadXML(invalidFile)).rejects.toThrow( + 'Not Found' + ); }); test('error callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadXML(invalidFile, () => { - console.log('here'); - reject('Success callback executed'); - }, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }); + mockP5Prototype.loadXML( + invalidFile, + () => { + console.log('here'); + reject('Success callback executed'); + }, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + } + ); }); }); test('success callback is called', async () => { await new Promise((resolve, reject) => { - mockP5Prototype.loadXML(validFile, () => { - // Wait a bit so that if both callbacks are executed we will get an error. - setTimeout(resolve, 50); - }, err => { - reject(`Error callback called: ${err.toString()}`); - }); + mockP5Prototype.loadXML( + validFile, + () => { + // Wait a bit so that if both callbacks are executed we will get an error. + setTimeout(resolve, 50); + }, + err => { + reject(`Error callback called: ${err.toString()}`); + } + ); }); }); diff --git a/test/unit/io/saveTable.js b/test/unit/io/saveTable.js index 691432d53e..8e12a6e82d 100644 --- a/test/unit/io/saveTable.js +++ b/test/unit/io/saveTable.js @@ -11,18 +11,18 @@ const mockAnchorElement = vi.mockObject({ }); const originalCreateElement = document.createElement; vi.spyOn(document, 'createElement').mockImplementation((...args) => { - if(args[0] !== 'a'){ + if (args[0] !== 'a') { return originalCreateElement.apply(document, args); - }else{ + } else { return mockAnchorElement; } }); -suite('saveTable', function() { +suite('saveTable', function () { const validFile = '/test/unit/assets/csv.csv'; let myTable; - beforeAll(async function() { + beforeAll(async function () { files(mockP5, mockP5Prototype); table(mockP5, mockP5Prototype); tableRow(mockP5, mockP5Prototype); @@ -33,7 +33,7 @@ suite('saveTable', function() { vi.clearAllMocks(); }); - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.saveTable); assert.typeOf(mockP5Prototype.saveTable, 'function'); }); diff --git a/test/unit/math/calculation.js b/test/unit/math/calculation.js index 82868210b8..5424c1ab23 100644 --- a/test/unit/math/calculation.js +++ b/test/unit/math/calculation.js @@ -1,472 +1,477 @@ import calculation from '../../../src/math/calculation.js'; import { vi } from 'vitest'; -suite('Calculation', function() { +suite('Calculation', function () { const mockP5 = { _validateParameters: vi.fn() }; const mockP5Prototype = {}; - beforeAll(function() { + beforeAll(function () { calculation(mockP5, mockP5Prototype); }); - afterAll(function() { - }); + afterAll(function () {}); - suite('p5.prototype.abs', function() { + suite('p5.prototype.abs', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.abs); assert.typeOf(mockP5Prototype.abs, 'function'); }); - test('should return a number', function() { + test('should return a number', function () { result = mockP5Prototype.abs(); assert.typeOf(result, 'number'); }); - test('should return an absolute value', function() { + test('should return an absolute value', function () { result = mockP5Prototype.abs(-1); assert.equal(result, 1); assert.notEqual(result, -1); }); }); - suite('p5.prototype.ceil', function() { + suite('p5.prototype.ceil', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.ceil); assert.typeOf(mockP5Prototype.ceil, 'function'); }); - test('should return ceil value given negative value', function() { + test('should return ceil value given negative value', function () { result = mockP5Prototype.ceil(-1.9); assert.equal(result, -1); }); - test('should return a ceil value given positive value', function() { + test('should return a ceil value given positive value', function () { result = mockP5Prototype.ceil(0.1); assert.equal(result, 1); }); - test('should return same number', function() { + test('should return same number', function () { result = mockP5Prototype.ceil(1); assert.equal(result, 1); }); }); - suite('p5.prototype.dist', function() { + suite('p5.prototype.dist', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.dist); assert.typeOf(mockP5Prototype.dist, 'function'); }); - test('should return a number', function() { + test('should return a number', function () { result = mockP5Prototype.dist(0, 0, 2, 3); assert.typeOf(result, 'number'); }); - test('should return correct distance', function() { + test('should return correct distance', function () { result = mockP5Prototype.dist(0, 0, 2, 3); assert.approximately(result, 3.605551, 0.000001); // Math.hypot(2, 3) }); - test('should return positive distance', function() { + test('should return positive distance', function () { result = mockP5Prototype.dist(0, 0, -2, -3); assert.approximately(result, 3.605551, 0.000001); // Math.hypot(2, 3) }); - test('should return correct distance', function() { + test('should return correct distance', function () { result = mockP5Prototype.dist(0, 0, 0, 2, 3, 5); assert.approximately(result, 6.164414, 0.000001); // Math.hypot(2, 3, 5) }); - test('should return positive distance', function() { + test('should return positive distance', function () { result = mockP5Prototype.dist(0, 0, 0, -2, -3, 5); assert.approximately(result, 6.164414, 0.000001); // Math.hypot(2, 3, 5) }); - test('should not underflow', function() { + test('should not underflow', function () { result = mockP5Prototype.dist(0, 0, 1e-200, 2e-200); assert.notEqual(result, 0); }); - test('should not overflow', function() { + test('should not overflow', function () { result = mockP5Prototype.dist(0, 0, 1e200, 2e200); assert.notEqual(result, Infinity); }); - test('should return 0 for identical 2D points', function() { + test('should return 0 for identical 2D points', function () { result = mockP5Prototype.dist(2, 3, 2, 3); assert.equal(result, 0); }); - test('should return 0 for identical 3D points', function() { + test('should return 0 for identical 3D points', function () { result = mockP5Prototype.dist(2, 3, 5, 2, 3, 5); assert.equal(result, 0); }); - test('should return infinity if coordinate of a point is at infinity (2D)', function() { + test('should return infinity if coordinate of a point is at infinity (2D)', function () { result = mockP5Prototype.dist(0, 0, Infinity, 0); assert.equal(result, Infinity); }); - test('should return infinity if coordinate of a point is at -infinity (2D)', function() { + test('should return infinity if coordinate of a point is at -infinity (2D)', function () { result = mockP5Prototype.dist(0, 0, -Infinity, 0); assert.equal(result, Infinity); }); - test('should handle overflow correctly (2D)', function() { + test('should handle overflow correctly (2D)', function () { result = mockP5Prototype.dist(0, 1e200, 0, 1e199); assert.equal(result, 9e199); }); - test('should handle rounding correctly (2D)', function() { + test('should handle rounding correctly (2D)', function () { result = mockP5Prototype.dist(0, 1e-200, 0, 1e-199); assert.equal(result, 9e-200); }); - test('should handle string parameters correctly (2D)', function() { + test('should handle string parameters correctly (2D)', function () { result = mockP5Prototype.dist(0, 0, '4', '3'); assert.equal(result, 5); }); - test('should return infinity if coordinate of a point is at infinity (3D)', function() { + test('should return infinity if coordinate of a point is at infinity (3D)', function () { result = mockP5Prototype.dist(0, 0, 0, Infinity, 0, 0); assert.equal(result, Infinity); }); - test('should return infinity if coordinate of a point is at -infinity (3D)', function() { + test('should return infinity if coordinate of a point is at -infinity (3D)', function () { result = mockP5Prototype.dist(0, 0, 0, -Infinity, 0, 0); assert.equal(result, Infinity); }); - test('should handle overflow correctly (3D)', function() { + test('should handle overflow correctly (3D)', function () { result = mockP5Prototype.dist(0, 0, 1e200, 0, 0, 1e199); assert.equal(result, 9e199); }); - test('should handle rounding correctly (3D)', function() { + test('should handle rounding correctly (3D)', function () { result = mockP5Prototype.dist(0, 0, 1e-200, 0, 0, 1e-199); assert.equal(result, 9e-200); }); - test('should handle string parameters correctly (3D)', function() { + test('should handle string parameters correctly (3D)', function () { result = mockP5Prototype.dist(0, 0, 0, '4', '4', '2'); assert.equal(result, 6); }); }); - suite('p5.prototype.exp', function() { + suite('p5.prototype.exp', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.exp); assert.typeOf(mockP5Prototype.exp, 'function'); }); - test('should return exp value given negative value', function() { + test('should return exp value given negative value', function () { result = mockP5Prototype.exp(-1); assert.approximately(result, Math.exp(-1), 0.000001); }); - test('should return exp value given positive value', function() { + test('should return exp value given positive value', function () { result = mockP5Prototype.exp(1); assert.approximately(result, Math.exp(1), 0.000001); }); - test('should return 1', function() { + test('should return 1', function () { result = mockP5Prototype.exp(0); assert.equal(result, 1); }); }); - suite('p5.prototype.floor', function() { + suite('p5.prototype.floor', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.floor); assert.typeOf(mockP5Prototype.floor, 'function'); }); - test('should return floor value given negative value', function() { + test('should return floor value given negative value', function () { result = mockP5Prototype.floor(-1.9); assert.equal(result, -2); }); - test('should return a floor value given positive value', function() { + test('should return a floor value given positive value', function () { result = mockP5Prototype.floor(0.1); assert.equal(result, 0); }); - test('should return same number', function() { + test('should return same number', function () { result = mockP5Prototype.floor(1); assert.equal(result, 1); }); }); - suite('p5.prototype.lerp', function() { + suite('p5.prototype.lerp', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.lerp); assert.typeOf(mockP5Prototype.lerp, 'function'); }); - test('should return start', function() { + test('should return start', function () { result = mockP5Prototype.lerp(0, 5, 0); assert.equal(result, 0); }); - test('should return average', function() { + test('should return average', function () { result = mockP5Prototype.lerp(0, 5, 0.5); assert.equal(result, 2.5); }); - test('should return stop', function() { + test('should return stop', function () { result = mockP5Prototype.lerp(0, 5, 1); assert.equal(result, 5); }); }); - suite('p5.prototype.log', function() { + suite('p5.prototype.log', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.log); assert.typeOf(mockP5Prototype.log, 'function'); }); - test('should return log value given negative value', function() { + test('should return log value given negative value', function () { result = mockP5Prototype.log(Math.exp(-1)); assert.approximately(result, -1, 0.0001); }); - test('should return log value given positive value', function() { + test('should return log value given positive value', function () { result = mockP5Prototype.log(Math.exp(1)); assert.approximately(result, 1, 0.0001); }); - test('should return 0', function() { + test('should return 0', function () { result = mockP5Prototype.log(Math.exp(0)); assert.equal(result, 0); }); }); - suite('p5.prototype.mag', function() { + suite('p5.prototype.mag', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.mag); assert.typeOf(mockP5Prototype.mag, 'function'); }); - test('should return a number', function() { + test('should return a number', function () { result = mockP5Prototype.mag(2, 3); assert.typeOf(result, 'number'); }); - test('should return correct magitude', function() { + test('should return correct magitude', function () { result = mockP5Prototype.mag(2, 3); assert.approximately(result, 3.605551, 0.000001); // Math.hypot(2, 3) }); - test('should return positive magnitude given negative inputs', function() { + test('should return positive magnitude given negative inputs', function () { result = mockP5Prototype.mag(-2, -3); assert.approximately(result, 3.605551, 0.000001); // Math.hypot(2, 3) }); }); - suite('p5.prototype.map', function() { + suite('p5.prototype.map', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.map); assert.typeOf(mockP5Prototype.map, 'function'); }); - test('should return a number', function() { + test('should return a number', function () { result = mockP5Prototype.map(1, 0, 10, 0, 20); assert.typeOf(result, 'number'); }); - test('should return scaled value', function() { + test('should return scaled value', function () { result = mockP5Prototype.map(1, 0, 10, 0, 20); assert.equal(result, 2); }); - test('should extrapolate by default', function() { + test('should extrapolate by default', function () { assert.approximately(mockP5Prototype.map(10, 0, 1, 10, 11), 20, 0.01); assert.approximately(mockP5Prototype.map(-1, 0, 1, 10, 11), 9, 0.01); assert.approximately(mockP5Prototype.map(2, 0, 1, 20, 10), 0, 0.01); }); - test('shaould clamp correctly', function() { + test('shaould clamp correctly', function () { assert.approximately(mockP5Prototype.map(1, 0, 10, 0, 20, true), 2, 0.01); assert.approximately( - mockP5Prototype.map(10, 0, 1, 10, 11, true), 11, 0.01 + mockP5Prototype.map(10, 0, 1, 10, 11, true), + 11, + 0.01 ); assert.approximately( - mockP5Prototype.map(-1, 0, 1, 10, 11, true), 10, 0.01 + mockP5Prototype.map(-1, 0, 1, 10, 11, true), + 10, + 0.01 ); assert.approximately( - mockP5Prototype.map(2, 0, 1, 20, 10, true), 10, 0.01 + mockP5Prototype.map(2, 0, 1, 20, 10, true), + 10, + 0.01 ); }); }); - suite('p5.prototype.max', function() { + suite('p5.prototype.max', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.max); assert.typeOf(mockP5Prototype.max, 'function'); }); - test('should return larger left argument', function() { + test('should return larger left argument', function () { result = mockP5Prototype.max(10, -1); assert.equal(result, 10); }); - test('should return larger right argument', function() { + test('should return larger right argument', function () { result = mockP5Prototype.max(-1, 10); assert.equal(result, 10); }); - test('should return single value', function() { + test('should return single value', function () { result = mockP5Prototype.max(10, 10); assert.equal(result, 10); }); - test('should return larger value from array', function() { + test('should return larger value from array', function () { result = mockP5Prototype.max([10, -1]); assert.equal(result, 10); }); - test('should return larger value from array', function() { + test('should return larger value from array', function () { result = mockP5Prototype.max(-1, 10); assert.equal(result, 10); }); - test('should return single value from array', function() { + test('should return single value from array', function () { result = mockP5Prototype.max([10, 10]); assert.equal(result, 10); }); - test('should handle Infinity as a valid argument', function() { + test('should handle Infinity as a valid argument', function () { result = mockP5Prototype.max(Infinity, 42); assert.equal(result, Infinity); }); - test('should handle -Infinity as a valid argument', function() { + test('should handle -Infinity as a valid argument', function () { result = mockP5Prototype.max(-Infinity, 42); assert.equal(result, 42); }); }); - suite('p5.prototype.min', function() { + suite('p5.prototype.min', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.min); assert.typeOf(mockP5Prototype.min, 'function'); }); - test('should return smaller right argument', function() { + test('should return smaller right argument', function () { result = mockP5Prototype.min(10, -1); assert.equal(result, -1); }); - test('should return smaller left argument', function() { + test('should return smaller left argument', function () { result = mockP5Prototype.min(-1, 10); assert.equal(result, -1); }); - test('should return single value', function() { + test('should return single value', function () { result = mockP5Prototype.min(10, 10); assert.equal(result, 10); }); - test('should return smaller value from array', function() { + test('should return smaller value from array', function () { result = mockP5Prototype.min([10, -1]); assert.equal(result, -1); }); - test('should return smaller value from array', function() { + test('should return smaller value from array', function () { result = mockP5Prototype.min([-1, 10]); assert.equal(result, -1); }); - test('should return single value from array', function() { + test('should return single value from array', function () { result = mockP5Prototype.min([10, 10]); assert.equal(result, 10); }); - test('should handle Infinity as a valid argument', function() { + test('should handle Infinity as a valid argument', function () { result = mockP5Prototype.min(Infinity, 42); assert.equal(result, 42); }); - test('should handle -Infinity as a valid argument', function() { + test('should handle -Infinity as a valid argument', function () { result = mockP5Prototype.min(-Infinity, 42); assert.equal(result, -Infinity); }); }); - suite('p5.prototype.norm', function() { + suite('p5.prototype.norm', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.norm); assert.typeOf(mockP5Prototype.norm, 'function'); }); - test('should return scaled decimal value', function() { + test('should return scaled decimal value', function () { // note: there is currently scoping issues with "this" keyword result = mockP5Prototype.norm(20, 0, 50); assert.equal(result, 0.4); }); }); - suite('p5.prototype.constrain', function() { + suite('p5.prototype.constrain', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.constrain); assert.typeOf(mockP5Prototype.constrain, 'function'); }); - test('should return same number', function() { + test('should return same number', function () { result = mockP5Prototype.constrain(1, 3, 5); assert.equal(result, 3); }); - test('should return lower bound', function() { + test('should return lower bound', function () { result = mockP5Prototype.constrain(1, -1, 5); assert.equal(result, 1); }); - test('should return upper bound', function() { + test('should return upper bound', function () { result = mockP5Prototype.constrain(1, 10, 5); assert.equal(result, 10); }); }); - suite('p5.prototype.sq', function() { + suite('p5.prototype.sq', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.sq); assert.typeOf(mockP5Prototype.sq, 'function'); }); - test('should return sauare value', function() { + test('should return sauare value', function () { result = mockP5Prototype.sq(10); assert.equal(result, 100); }); - test('should return squared value given negative number', function() { + test('should return squared value given negative number', function () { result = mockP5Prototype.sq(-10); assert.equal(result, 100); }); }); - suite('p5.prototype.pow', function() { + suite('p5.prototype.pow', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.pow); assert.typeOf(mockP5Prototype.pow, 'function'); }); - test('should return pow for negative exponential', function() { + test('should return pow for negative exponential', function () { result = mockP5Prototype.pow(2, -1); assert.equal(result, 0.5); }); - test('should return pow for positive exponential', function() { + test('should return pow for positive exponential', function () { result = mockP5Prototype.pow(2, 4); assert.equal(result, 16); }); }); - suite('p5.prototype.round', function() { + suite('p5.prototype.round', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.round); assert.typeOf(mockP5Prototype.round, 'function'); }); - test('should round down', function() { + test('should round down', function () { result = mockP5Prototype.round(2.1); assert.equal(result, 2); }); - test('should round up from midpoint', function() { + test('should round up from midpoint', function () { result = mockP5Prototype.round(2.5); assert.equal(result, 3); }); - test('should round up', function() { + test('should round up', function () { result = mockP5Prototype.round(2.8); assert.equal(result, 3); }); - test('should round two decimal places', function() { + test('should round two decimal places', function () { result = mockP5Prototype.round(12.31833, 2); assert.equal(result, 12.32); }); - test('should round very small numbers to zero', function() { + test('should round very small numbers to zero', function () { result = mockP5Prototype.round(1.234567e-14); assert.equal(result, 0); }); - test('should round very small numbers to zero when decimal places are specified', function() { + test('should round very small numbers to zero when decimal places are specified', function () { result = mockP5Prototype.round(1.234567e-14, 2); assert.equal(result, 0); }); }); - suite('p5.prototype.sqrt', function() { + suite('p5.prototype.sqrt', function () { var result; - test('should be a function', function() { + test('should be a function', function () { assert.ok(mockP5Prototype.sqrt); assert.typeOf(mockP5Prototype.sqrt, 'function'); }); - test('should return square root', function() { + test('should return square root', function () { result = mockP5Prototype.sqrt(100); assert.equal(result, 10); }); diff --git a/test/unit/math/noise.js b/test/unit/math/noise.js index 3b95dd403f..16ebdc1afa 100644 --- a/test/unit/math/noise.js +++ b/test/unit/math/noise.js @@ -1,18 +1,17 @@ import noise from '../../../src/math/noise.js'; import { vi } from 'vitest'; -suite('Noise', function() { +suite('Noise', function () { const mockP5 = { _validateParameters: vi.fn() }; const mockP5Prototype = {}; - beforeAll(function() { + beforeAll(function () { noise(mockP5, mockP5Prototype); }); - afterAll(function() { - }); + afterAll(function () {}); // This could use some better testing! // Just checking that we get an actual number now @@ -21,22 +20,22 @@ suite('Noise', function() { var result; var results = []; - suite('p5.prototype.noise', function() { - beforeEach(function() { + suite('p5.prototype.noise', function () { + beforeEach(function () { result = mockP5Prototype.noise(0); }); - test('should return a number', function() { + test('should return a number', function () { assert.typeOf(result, 'number'); }); - test('should return a number 0 < n < 1', function() { + test('should return a number 0 < n < 1', function () { assert.isTrue(result > 0); assert.isTrue(result < 1); }); }); // Test for noiseSeed - suite('p5.prototype.noiseSeed', function() { - beforeEach(function() { + suite('p5.prototype.noiseSeed', function () { + beforeEach(function () { mockP5Prototype.noiseSeed(99); var t = 0; for (var i = 0; i < 5; i++) { @@ -50,14 +49,14 @@ suite('Noise', function() { t += 0.01; } }); - test('should return a number 0 < n < 1', function() { + test('should return a number 0 < n < 1', function () { for (var i = 0; i < results.length; i++) { assert.typeOf(results[i], 'number'); assert.isTrue(results[i] > 0); assert.isTrue(results[i] < 1); } }); - test('should return same sequence of numbers', function() { + test('should return same sequence of numbers', function () { for (var i = 0; i < 5; i++) { assert.isTrue(results[i] === results[i + 5]); } diff --git a/test/unit/math/p5.Matrix.js b/test/unit/math/p5.Matrix.js index acce52506c..762d7f3779 100644 --- a/test/unit/math/p5.Matrix.js +++ b/test/unit/math/p5.Matrix.js @@ -13,7 +13,6 @@ var other = Float32Array.from([ var mat3 = Float32Array.from([1, 2, 3, 4, 5, 6, 7, 8, 9]); - suite('p5.Matrix', function () { var myp5; @@ -39,7 +38,6 @@ suite('p5.Matrix', function () { [].slice.call(m.mat4), [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] ); - }); test('new p5.Matrix(array)', function () { @@ -64,7 +62,6 @@ suite('p5.Matrix', function () { expect(toArray(m.mat4)).toEqual([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); - }); }); @@ -154,13 +151,11 @@ suite('p5.Matrix', function () { suite.todo('add', () => {}); suite('mult', function () { - var mm = [ 30, 70, 110, 150, 70, 174, 278, 382, 110, 278, 446, 614, 150, 382, 614, 846 ]; - test('self', function () { var m = new p5.Matrix(mat4.slice()); m.mult(m); @@ -172,7 +167,6 @@ suite('p5.Matrix', function () { 542, 600 ] ); - }); test('p5.Matrix', function () { @@ -196,13 +190,11 @@ suite('p5.Matrix', function () { }); suite('apply', function () { - var am = [ 276, 304, 332, 360, 304, 336, 368, 400, 332, 368, 404, 440, 360, 400, 440, 480 ]; - test('self', function () { var m = new p5.Matrix(mat4.slice()); m.apply(m); @@ -214,7 +206,6 @@ suite('p5.Matrix', function () { 542, 600 ] ); - }); test('p5.Matrix', function () { @@ -238,10 +229,8 @@ suite('p5.Matrix', function () { }); suite('scale', function () { - var sm = [2, 4, 6, 8, 15, 18, 21, 24, 45, 50, 55, 60, 13, 14, 15, 16]; - var mat4 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; test('p5.Vector', function () { var m = new p5.Matrix(mat4.slice()); @@ -267,7 +256,6 @@ suite('p5.Matrix', function () { }); suite('rotate', function () { - var rm = Float32Array.from([ 1.433447866601989, 2.5241247073503885, 3.6148015480987885, 4.7054783888471885, 6.460371405020393, 7.054586073938033, @@ -275,7 +263,6 @@ suite('p5.Matrix', function () { 9.157598472697025, 10.36479893504708, 11.571999397397136, 13, 14, 15, 16 ]); - test('p5.Vector', function () { var m = new p5.Matrix(mat4.slice()); var v = myp5.createVector(2, 3, 5); @@ -329,19 +316,19 @@ suite('p5.Matrix', function () { const multMatrix = new p5.Matrix([1, 1, 1, 0, 1, 1, 1, 0, 1]); // When taking a matrix as an argument m.mult(multMatrix); - expect(toArray(m.mat3)).toEqual([ 4, 3, 6, 10, 9, 15, 16, 15, 24 ]); + expect(toArray(m.mat3)).toEqual([4, 3, 6, 10, 9, 15, 16, 15, 24]); }); test('mult a 3x3 matrix with array as argument', function () { const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); m.mult([1, 1, 1, 0, 1, 1, 1, 0, 1]); - expect(toArray(m.mat3)).toEqual([ 4, 3, 6, 10, 9, 15, 16, 15, 24 ]); + expect(toArray(m.mat3)).toEqual([4, 3, 6, 10, 9, 15, 16, 15, 24]); }); test('mult a 3x3 matrix with arguments non array', function () { const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); m.mult(1, 1, 1, 0, 1, 1, 1, 0, 1); - expect(toArray(m.mat3)).toEqual([ 4, 3, 6, 10, 9, 15, 16, 15, 24 ]); + expect(toArray(m.mat3)).toEqual([4, 3, 6, 10, 9, 15, 16, 15, 24]); }); test('column() and row()', function () { @@ -435,7 +422,8 @@ suite('p5.Matrix', function () { expect(mat.mat3).toEqual([1, 4, 7, 2, 5, 8, 3, 6, 9]); }); }); - describe.skip('Determinant', () => { // TODO: Cristian, when this is public we'll add tests + describe.skip('Determinant', () => { + // TODO: Cristian, when this is public we'll add tests it('should calculate the determinant of a 4x4 matrix', () => { const mat4 = new p5.Matrix([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 @@ -494,8 +482,9 @@ suite('p5.Matrix', function () { const matrix = new p5.Matrix([1, 2, 3, 0, 1, 4, 5, 6, 0]); const invertedMatrix = matrix.invert(); - expect(toArray(invertedMatrix.mat3)) - .toEqual([-24, 18, 5, 20, -15, -4, -5, 4, 1]); + expect(toArray(invertedMatrix.mat3)).toEqual([ + -24, 18, 5, 20, -15, -4, -5, 4, 1 + ]); }); it('should return null for a non-invertible 3x3 matrix', () => { diff --git a/test/unit/math/p5.Vector.js b/test/unit/math/p5.Vector.js index bbe7c38a5b..f951d6544a 100644 --- a/test/unit/math/p5.Vector.js +++ b/test/unit/math/p5.Vector.js @@ -1,15 +1,17 @@ import { default as vector, Vector } from '../../../src/math/p5.Vector.js'; import { default as math } from '../../../src/math/math.js'; -import { _defaultEmptyVector, _validatedVectorOperation } from '../../../src/math/patch-vector.js'; +import { + _defaultEmptyVector, + _validatedVectorOperation +} from '../../../src/math/patch-vector.js'; import { vi } from 'vitest'; - suite('p5.Vector', function () { var v; let FESCalled = false; const mockP5 = { - _friendlyError: function(msg, func) { + _friendlyError: function (msg, func) { FESCalled = true; console.warn(msg); } @@ -30,11 +32,26 @@ suite('p5.Vector', function () { ); // The following mocks simulate the validation decorator - Vector.prototype.add = _validatedVectorOperation(false)(Vector.prototype.add, options); - Vector.prototype.sub = _validatedVectorOperation(false)(Vector.prototype.sub, options); - Vector.prototype.mult = _validatedVectorOperation(true)(Vector.prototype.mult, options); - Vector.prototype.rem = _validatedVectorOperation(true)(Vector.prototype.rem, options); - Vector.prototype.div = _validatedVectorOperation(true)(Vector.prototype.div, options); + Vector.prototype.add = _validatedVectorOperation(false)( + Vector.prototype.add, + options + ); + Vector.prototype.sub = _validatedVectorOperation(false)( + Vector.prototype.sub, + options + ); + Vector.prototype.mult = _validatedVectorOperation(true)( + Vector.prototype.mult, + options + ); + Vector.prototype.rem = _validatedVectorOperation(true)( + Vector.prototype.rem, + options + ); + Vector.prototype.div = _validatedVectorOperation(true)( + Vector.prototype.div, + options + ); }); afterEach(function () {}); @@ -102,7 +119,6 @@ suite('p5.Vector', function () { }); }); - suite.todo('p5.prototype.createVector()', function () { beforeEach(function () { v = mockP5Prototype.createVector(); @@ -116,7 +132,7 @@ suite('p5.Vector', function () { }); test('should have values be initialized to 0,0,0', function () { - assert.deepEqual(v.values, [0,0,0]); + assert.deepEqual(v.values, [0, 0, 0]); }); test('should have dimensions initialized to 3', function () { @@ -296,15 +312,12 @@ suite('p5.Vector', function () { expect(v1.angleBetween(v2)).to.equal(0); }); - test.todo( - 'between [0,3,0] and [0,-3,0] should be 180 degrees', - function () { - mockP5Prototype.angleMode(DEGREES); - v1 = new Vector(0, 3, 0); - v2 = new Vector(0, -3, 0); - expect(v1.angleBetween(v2)).to.be.closeTo(180, 0.01); - } - ); + test.todo('between [0,3,0] and [0,-3,0] should be 180 degrees', function () { + mockP5Prototype.angleMode(DEGREES); + v1 = new Vector(0, 3, 0); + v2 = new Vector(0, -3, 0); + expect(v1.angleBetween(v2)).to.be.closeTo(180, 0.01); + }); test('between [1,0,0] and [2,2,0] should be 1/4 PI radians', function () { v1 = new Vector(1, 0, 0); @@ -358,15 +371,12 @@ suite('p5.Vector', function () { expect(Vector.angleBetween(v2, v1)).to.be.NaN; }); - test.todo( - 'between [1,0,0] and [0,-1,0] should be -90 degrees', - function () { - mockP5Prototype.angleMode(DEGREES); - v1 = new Vector(1, 0, 0); - v2 = new Vector(0, -1, 0); - expect(Vector.angleBetween(v1, v2)).to.be.closeTo(-90, 0.01); - } - ); + test.todo('between [1,0,0] and [0,-1,0] should be -90 degrees', function () { + mockP5Prototype.angleMode(DEGREES); + v1 = new Vector(1, 0, 0); + v2 = new Vector(0, -1, 0); + expect(Vector.angleBetween(v1, v2)).to.be.closeTo(-90, 0.01); + }); test('between [0,3,0] and [0,-3,0] should be PI radians', function () { v1 = new Vector(0, 3, 0); @@ -1018,7 +1028,6 @@ suite('p5.Vector', function () { }); }); - suite('smaller dimension', function () { let v1, v2, v3; beforeEach(function () { @@ -1052,10 +1061,10 @@ suite('p5.Vector', function () { }); test('should be prioritized in div()', function () { - assert.deepEqual(v1.div(v2).values, [1/2]); + assert.deepEqual(v1.div(v2).values, [1 / 2]); expect(v1.div(v2).dimensions).to.eql(1); - assert.deepEqual(v3.div(v2).values, [2, 5/3]); + assert.deepEqual(v3.div(v2).values, [2, 5 / 3]); expect(v3.div(v2).dimensions).to.eql(2); }); @@ -1397,7 +1406,7 @@ suite('p5.Vector', function () { suite('heading', function () { beforeEach(function () { - v = new Vector(0,0,0); + v = new Vector(0, 0, 0); }); suite('p5.Vector.prototype.heading() [INSTANCE]', function () { @@ -1487,10 +1496,10 @@ suite('p5.Vector', function () { expect(v.lerp()).to.eql(v); }); - suite('with p5.Vector', function() { - test('should call lerp with 4 arguments', function() { + suite('with p5.Vector', function () { + test('should call lerp with 4 arguments', function () { vi.spyOn(v, 'lerp'); - v.lerp(new Vector(1,2,3), 1); + v.lerp(new Vector(1, 2, 3), 1); expect(v.lerp).toHaveBeenCalledWith(1, 2, 3, 1); }); }); @@ -1725,35 +1734,19 @@ suite('p5.Vector', function () { incoming_x = 1; incoming_y = 1; incoming_z = 1; - original_incoming = new Vector( - incoming_x, - incoming_y, - incoming_z - ); + original_incoming = new Vector(incoming_x, incoming_y, incoming_z); x_normal = new Vector(3, 0, 0); y_normal = new Vector(0, 3, 0); z_normal = new Vector(0, 0, 3); - x_bounce_incoming = new Vector( - incoming_x, - incoming_y, - incoming_z - ); + x_bounce_incoming = new Vector(incoming_x, incoming_y, incoming_z); x_bounce_outgoing = x_bounce_incoming.reflect(x_normal); - y_bounce_incoming = new Vector( - incoming_x, - incoming_y, - incoming_z - ); + y_bounce_incoming = new Vector(incoming_x, incoming_y, incoming_z); y_bounce_outgoing = y_bounce_incoming.reflect(y_normal); - z_bounce_incoming = new Vector( - incoming_x, - incoming_y, - incoming_z - ); + z_bounce_incoming = new Vector(incoming_x, incoming_y, incoming_z); z_bounce_outgoing = z_bounce_incoming.reflect(z_normal); }); @@ -1836,11 +1829,7 @@ suite('p5.Vector', function () { incoming_x = 1; incoming_y = 1; incoming_z = 1; - original_incoming = new Vector( - incoming_x, - incoming_y, - incoming_z - ); + original_incoming = new Vector(incoming_x, incoming_y, incoming_z); x_target = new Vector(0, 0, 0); y_target = new Vector(0, 0, 0); z_target = new Vector(0, 0, 0); @@ -1849,33 +1838,21 @@ suite('p5.Vector', function () { y_normal = new Vector(0, 3, 0); z_normal = new Vector(0, 0, 3); - x_bounce_incoming = new Vector( - incoming_x, - incoming_y, - incoming_z - ); + x_bounce_incoming = new Vector(incoming_x, incoming_y, incoming_z); x_bounce_outgoing = Vector.reflect( x_bounce_incoming, x_normal, x_target ); - y_bounce_incoming = new Vector( - incoming_x, - incoming_y, - incoming_z - ); + y_bounce_incoming = new Vector(incoming_x, incoming_y, incoming_z); y_bounce_outgoing = Vector.reflect( y_bounce_incoming, y_normal, y_target ); - z_bounce_incoming = new Vector( - incoming_x, - incoming_y, - incoming_z - ); + z_bounce_incoming = new Vector(incoming_x, incoming_y, incoming_z); z_bounce_outgoing = Vector.reflect( z_bounce_incoming, z_normal, @@ -2120,14 +2097,12 @@ suite('p5.Vector', function () { assert.equal(vect.getValue(3), 4); }); - test('should throw friendly error if attempting to get element outside length', - function () { - let vect = new Vector(1, 2, 3, 4); - FESCalled = false; - assert.equal(vect.getValue(5), undefined); - assert.equal(FESCalled, true); - } - ); + test('should throw friendly error if attempting to get element outside length', function () { + let vect = new Vector(1, 2, 3, 4); + FESCalled = false; + assert.equal(vect.getValue(5), undefined); + assert.equal(FESCalled, true); + }); }); suite('set value', function () { @@ -2140,14 +2115,12 @@ suite('p5.Vector', function () { assert.equal(vect.getValue(3), 4); }); - test('should throw friendly error if attempting to set element outside lenght', - function () { - let vect = new Vector(1, 2, 3, 4); - FESCalled = false; - vect.setValue(100, 7); - assert.equal(FESCalled, true); - } - ); + test('should throw friendly error if attempting to set element outside lenght', function () { + let vect = new Vector(1, 2, 3, 4); + FESCalled = false; + vect.setValue(100, 7); + assert.equal(FESCalled, true); + }); }); describe('get w', () => { diff --git a/test/unit/math/random.js b/test/unit/math/random.js index 0105b5ade8..39494985da 100644 --- a/test/unit/math/random.js +++ b/test/unit/math/random.js @@ -1,24 +1,23 @@ import random from '../../../src/math/random.js'; import { vi } from 'vitest'; -suite('Random', function() { +suite('Random', function () { const mockP5 = { _validateParameters: vi.fn() }; - const mockP5Prototype = { - }; + const mockP5Prototype = {}; - beforeAll(function() { + beforeAll(function () { random(mockP5, mockP5Prototype); }); - suite('p5.prototype.random', function() { + suite('p5.prototype.random', function () { var result; var results = []; - suite('random()', function() { - beforeEach(function() { + suite('random()', function () { + beforeEach(function () { mockP5Prototype.randomSeed(99); for (var i = 0; i < 5; i++) { results[i] = mockP5Prototype.random(); @@ -28,77 +27,77 @@ suite('Random', function() { results[i] = mockP5Prototype.random(); } }); - test('should return a number', function() { + test('should return a number', function () { for (var i = 0; i < 10; i++) { assert.typeOf(results[i], 'number'); } }); - test('should return a number 0 <= n < 1', function() { + test('should return a number 0 <= n < 1', function () { for (var i = 0; i < 10; i++) { assert.isTrue(results[i] >= 0); assert.isTrue(results[i] < 1); } }); - test('should return same sequence of numbers', function() { + test('should return same sequence of numbers', function () { for (var i = 0; i < 5; i++) { assert.isTrue(results[i] === results[i + 5]); } }); }); - suite('random(5)', function() { - test('should return a number 0 <= n < 5', function() { + suite('random(5)', function () { + test('should return a number 0 <= n < 5', function () { result = mockP5Prototype.random(5); assert.isTrue(result >= 0); assert.isTrue(result < 5); }); }); - suite('random(1, 10)', function() { - test('should return a number 1 <= n < 10', function() { + suite('random(1, 10)', function () { + test('should return a number 1 <= n < 10', function () { result = mockP5Prototype.random(1, 10); assert.isTrue(result >= 1); assert.isTrue(result < 10); }); }); - suite('random(["apple", "pear", "orange", "grape"])', function() { - test('should return a fruit', function() { + suite('random(["apple", "pear", "orange", "grape"])', function () { + test('should return a fruit', function () { var fruits = ['apple', 'pear', 'orange', 'grape']; result = mockP5Prototype.random(fruits); assert.include(fruits, result); }); }); }); - suite.skip('instance mode', function() { + suite.skip('instance mode', function () { var instances = []; - beforeEach(async function() { + beforeEach(async function () { var instanceCount = 2; for (var i = 0; i < instanceCount; i++) { - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { instances.push(p); }; }); } }); - afterEach(function() { - instances.forEach(function(instance) { + afterEach(function () { + instances.forEach(function (instance) { instance.remove(); }); }); - test('should be independent', function() { + test('should be independent', function () { var SEED = 42; - instances.forEach(function(instance) { + instances.forEach(function (instance) { instance.randomSeed(SEED); }); for (var i = 0; i < 10; i++) { - instances.reduce(function(prev, instance) { + instances.reduce(function (prev, instance) { var randomValue = instance.random(); if (prev != null) { assert.equal(randomValue, prev); @@ -110,36 +109,36 @@ suite('Random', function() { }); }); - suite('p5.prototype.randomGaussian', function() { - suite.skip('instance mode', function() { + suite('p5.prototype.randomGaussian', function () { + suite.skip('instance mode', function () { var instances = []; - beforeEach(async function() { + beforeEach(async function () { var instanceCount = 2; for (var i = 0; i < instanceCount; i++) { - new p5(function(p) { - p.setup = function() { + new p5(function (p) { + p.setup = function () { instances.push(p); }; }); } }); - afterEach(function() { - instances.forEach(function(instance) { + afterEach(function () { + instances.forEach(function (instance) { instance.remove(); }); }); - test('should be independent', function() { + test('should be independent', function () { var SEED = 42; - instances.forEach(function(instance) { + instances.forEach(function (instance) { instance.randomSeed(SEED); }); for (var i = 0; i < 10; i++) { - instances.reduce(function(prev, instance) { + instances.reduce(function (prev, instance) { var randomValue = instance.randomGaussian(0, 15); if (prev != null) { assert.equal(randomValue, prev); @@ -151,8 +150,8 @@ suite('Random', function() { }); }); - suite('randomGaussian(42, 0)', function() { - test('should return 42', function() { + suite('randomGaussian(42, 0)', function () { + test('should return 42', function () { let result = mockP5Prototype.randomGaussian(42, 0); assert.isTrue(result === 42); }); diff --git a/test/unit/math/trigonometry.js b/test/unit/math/trigonometry.js index 62b8bbd532..02c6ed3762 100644 --- a/test/unit/math/trigonometry.js +++ b/test/unit/math/trigonometry.js @@ -1,140 +1,140 @@ import trigonometry from '../../../src/math/trigonometry.js'; import { vi } from 'vitest'; -suite('Trigonometry', function() { +suite('Trigonometry', function () { let theta = 90; let x = 0; let y = 1; let ratio = 0.5; - const mockP5Prototype = { - }; + const mockP5Prototype = {}; const mockP5 = Object.create(mockP5Prototype, { _validateParameters: vi.fn() }); - beforeEach(async function() { + beforeEach(async function () { trigonometry(mockP5, mockP5Prototype); }); - afterAll(function() { - }); + afterAll(function () {}); - var handleDegreesAndRadians = function(func) { - test('should handle degrees', function() { + var handleDegreesAndRadians = function (func) { + test('should handle degrees', function () { mockP5Prototype.angleMode(mockP5.DEGREES); var degToRad = mockP5Prototype.radians(theta); assert.equal(Math[func](degToRad), mockP5Prototype[func](theta)); }); - test('should handle radians', function() { + test('should handle radians', function () { mockP5Prototype.angleMode(mockP5.RADIANS); assert.equal(Math[func](theta), mockP5Prototype[func](theta)); }); }; - var ahandleDegreesAndRadians = function(func) { - test('should handle degrees', function() { + var ahandleDegreesAndRadians = function (func) { + test('should handle degrees', function () { mockP5Prototype.angleMode(mockP5.DEGREES); assert.equal( - mockP5Prototype.degrees(Math[func](ratio)), mockP5Prototype[func](ratio) + mockP5Prototype.degrees(Math[func](ratio)), + mockP5Prototype[func](ratio) ); }); - test('should handle radians', function() { + test('should handle radians', function () { mockP5Prototype.angleMode(mockP5.RADIANS); assert.equal(Math[func](ratio), mockP5Prototype[func](ratio)); }); }; - suite('p5.prototype.angleMode', function() { - test('should set constant to DEGREES', function() { + suite('p5.prototype.angleMode', function () { + test('should set constant to DEGREES', function () { mockP5Prototype.angleMode(mockP5.DEGREES); assert.equal(mockP5Prototype.angleMode(), mockP5.DEGREES); }); - test('should set constant to RADIANS', function() { + test('should set constant to RADIANS', function () { mockP5Prototype.angleMode(mockP5Prototype.RADIANS); assert.equal(mockP5Prototype.angleMode(), mockP5.RADIANS); }); - test('should return radians', function() { + test('should return radians', function () { mockP5Prototype.angleMode(mockP5.RADIANS); assert.equal(mockP5Prototype.angleMode(), mockP5.RADIANS); }); - test('should return degrees', function() { + test('should return degrees', function () { mockP5Prototype.angleMode(mockP5.DEGREES); assert.equal(mockP5Prototype.angleMode(), mockP5.DEGREES); }); - test('should always be RADIANS or DEGREES', function() { + test('should always be RADIANS or DEGREES', function () { mockP5Prototype.angleMode('wtflolzkk'); assert.equal(mockP5Prototype.angleMode(), mockP5.RADIANS); }); }); - suite('p5.prototype.degrees', function() { - test('should return the angle in radians when angleMode is DEGREES', function() { + suite('p5.prototype.degrees', function () { + test('should return the angle in radians when angleMode is DEGREES', function () { mockP5Prototype.angleMode(mockP5.DEGREES); - var angleInRad = 360 * theta / (2 * Math.PI); // This is degToRad conversion + var angleInRad = (360 * theta) / (2 * Math.PI); // This is degToRad conversion assert.equal(mockP5Prototype.degrees(theta), angleInRad); }); - test('should return the angle in radians when angleMode is RADIANS', function() { + test('should return the angle in radians when angleMode is RADIANS', function () { mockP5Prototype.angleMode(mockP5.RADIANS); - var angleInRad = 360 * theta / (2 * Math.PI); // This is degToRad conversion + var angleInRad = (360 * theta) / (2 * Math.PI); // This is degToRad conversion assert.equal(mockP5Prototype.degrees(theta), angleInRad); }); }); - suite('p5.prototype.radians', function() { - test('should return the angle in degrees when angleMode is RADIANS', function() { + suite('p5.prototype.radians', function () { + test('should return the angle in degrees when angleMode is RADIANS', function () { mockP5Prototype.angleMode(mockP5.RADIANS); - var angleInDeg = 2 * Math.PI * theta / 360; // This is RadToDeg conversion + var angleInDeg = (2 * Math.PI * theta) / 360; // This is RadToDeg conversion assert.equal(mockP5Prototype.radians(theta), angleInDeg); }); - test('should return the angle in degrees when angleMode is DEGREES', function() { + test('should return the angle in degrees when angleMode is DEGREES', function () { mockP5Prototype.angleMode(mockP5.DEGREES); - var angleInDeg = 2 * Math.PI * theta / 360; // This is RadToDeg conversion + var angleInDeg = (2 * Math.PI * theta) / 360; // This is RadToDeg conversion assert.equal(mockP5Prototype.radians(theta), angleInDeg); }); }); - suite('p5.prototype.asin', function() { + suite('p5.prototype.asin', function () { ahandleDegreesAndRadians('asin'); }); - suite('p5.prototype.atan', function() { + suite('p5.prototype.atan', function () { ahandleDegreesAndRadians('atan'); }); - suite('p5.prototype.acos', function() { + suite('p5.prototype.acos', function () { ahandleDegreesAndRadians('acos'); }); - suite('p5.prototype.sin', function() { + suite('p5.prototype.sin', function () { handleDegreesAndRadians('sin'); }); - suite('p5.prototype.cos', function() { + suite('p5.prototype.cos', function () { handleDegreesAndRadians('cos'); }); - suite('p5.prototype.tan', function() { + suite('p5.prototype.tan', function () { handleDegreesAndRadians('tan'); }); - suite('p5.prototype.atan2', function() { - test('should handle degrees', function() { + suite('p5.prototype.atan2', function () { + test('should handle degrees', function () { mockP5Prototype.angleMode(mockP5.DEGREES); assert.equal( - mockP5Prototype.degrees(Math.atan2(y, x)), mockP5Prototype.atan2(y, x) + mockP5Prototype.degrees(Math.atan2(y, x)), + mockP5Prototype.atan2(y, x) ); }); - test('should handle radians', function() { + test('should handle radians', function () { mockP5Prototype.angleMode(mockP5.RADIANS); assert.equal(Math.atan2(y, x), mockP5Prototype.atan2(y, x)); }); diff --git a/test/unit/spec.js b/test/unit/spec.js index 7dd87068a7..fd9bbc759f 100644 --- a/test/unit/spec.js +++ b/test/unit/spec.js @@ -55,7 +55,7 @@ var spec = { // to omit some for speed if they should only be run manually. 'webgl', 'typography', - 'shape_modes', + 'shape_modes' ] }; document.write( diff --git a/test/unit/type/attributes.js b/test/unit/type/attributes.js index c6b1f26697..0315deac40 100644 --- a/test/unit/type/attributes.js +++ b/test/unit/type/attributes.js @@ -1,12 +1,12 @@ import p5 from '../../../src/app.js'; -suite('Typography Attributes', function() { +suite('Typography Attributes', function () { var myp5; beforeEach(function () { myp5 = new p5(function (p) { - p.setup = function () { }; - p.draw = function () { }; + p.setup = function () {}; + p.draw = function () {}; }); }); @@ -14,68 +14,70 @@ suite('Typography Attributes', function() { myp5.remove(); }); - suite('textLeading', function() { - test('sets and gets the leading value', function() { + suite('textLeading', function () { + test('sets and gets the leading value', function () { myp5.textLeading(20); assert.strictEqual(myp5.textLeading(), 20); }); - test('should work for negative leadings', function() { + test('should work for negative leadings', function () { myp5.textLeading(-20); assert.strictEqual(myp5.textLeading(), -20); }); }); - suite('textSize', function() { - test('sets and gets the text size', function() { + suite('textSize', function () { + test('sets and gets the text size', function () { myp5.textSize(24); assert.strictEqual(myp5.textSize(), 24); }); }); - suite('textStyle', function() { - test('sets and gets the text style', function() { + suite('textStyle', function () { + test('sets and gets the text style', function () { myp5.textStyle(myp5.ITALIC); assert.strictEqual(myp5.textStyle(), myp5.ITALIC); }); }); - suite('textWidth', function() { - test('should return a number for char input', function() { + suite('textWidth', function () { + test('should return a number for char input', function () { assert.isNumber(myp5.textWidth('P')); }); test('should return a number for string input.', function () { assert.isNumber(myp5.textWidth('p5.js')); }); // Either should not throw error - test('should return a number for number input', function() { + test('should return a number for number input', function () { assert.isNumber(myp5.textWidth(100)); }); - test('should ignore leading and trailing spaces', function() { + test('should ignore leading and trailing spaces', function () { assert.strictEqual(myp5.textWidth(' Hello '), myp5.textWidth('Hello')); - assert.strictEqual(myp5.textWidth(' Hello '), myp5.textWidth('Hello')); + assert.strictEqual( + myp5.textWidth(' Hello '), + myp5.textWidth('Hello') + ); }); }); - suite('textAscent', function() { - test('should return a number', function() { + suite('textAscent', function () { + test('should return a number', function () { assert.isNumber(myp5.textAscent()); }); }); - suite('textDescent', function() { - test('should return a number', function() { + suite('textDescent', function () { + test('should return a number', function () { assert.isNumber(myp5.textDescent()); }); }); - suite('textWrap', function() { - test('gets the default text wrap attribute', function() { + suite('textWrap', function () { + test('gets the default text wrap attribute', function () { assert.strictEqual(myp5.textWrap(), myp5.WORD); }); - test('sets and gets the text wrap value', function() { + test('sets and gets the text wrap value', function () { myp5.textWrap(myp5.CHAR); assert.strictEqual(myp5.textWrap(), myp5.CHAR); }); - }); }); diff --git a/test/unit/type/loading.js b/test/unit/type/loading.js index 8d90d6fb40..60483be761 100644 --- a/test/unit/type/loading.js +++ b/test/unit/type/loading.js @@ -5,8 +5,8 @@ suite('Loading Fonts', function () { beforeEach(function () { myp5 = new p5(function (p) { - p.setup = function () { }; - p.draw = function () { }; + p.setup = function () {}; + p.draw = function () {}; }); }); @@ -18,7 +18,9 @@ suite('Loading Fonts', function () { const fontFile = 'test/unit/assets/acmesa.ttf'; test('loadFont on zlib compressed fonts works', async () => { - const font = await myp5.loadFont('https://fonts.gstatic.com/s/montserrat/v29/JTUFjIg1_i6t8kCHKm459Wx7xQYXK0vOoz6jq6R8aXw.woff'); + const font = await myp5.loadFont( + 'https://fonts.gstatic.com/s/montserrat/v29/JTUFjIg1_i6t8kCHKm459Wx7xQYXK0vOoz6jq6R8aXw.woff' + ); expect(font.data).toBeTruthy(); }); @@ -29,24 +31,23 @@ suite('Loading Fonts', function () { assert.isTrue(pFont instanceof p5.Font); }); - test('loadFont.then', async () => new Promise(done => { - - myp5.loadFont(fontFile, 'acmesa').then(pFont => { - assert.ok(pFont, 'acmesa.ttf loaded'); - assert.equal(pFont.name, 'acmesa'); - assert.isTrue(pFont instanceof p5.Font); - done(); - }); - - })); - - test.skip('loadFont.callback', async () => new Promise(done => { - myp5.loadFont(fontFile, pFont => { - assert.ok(pFont, 'acmesa.ttf loaded'); - assert.equal(pFont.name, 'A.C.M.E. Secret Agent'); - assert.isTrue(pFont instanceof p5.Font); - done(); - }); - })); - + test('loadFont.then', async () => + new Promise(done => { + myp5.loadFont(fontFile, 'acmesa').then(pFont => { + assert.ok(pFont, 'acmesa.ttf loaded'); + assert.equal(pFont.name, 'acmesa'); + assert.isTrue(pFont instanceof p5.Font); + done(); + }); + })); + + test.skip('loadFont.callback', async () => + new Promise(done => { + myp5.loadFont(fontFile, pFont => { + assert.ok(pFont, 'acmesa.ttf loaded'); + assert.equal(pFont.name, 'A.C.M.E. Secret Agent'); + assert.isTrue(pFont instanceof p5.Font); + done(); + }); + })); }); diff --git a/test/unit/type/p5.Font.js b/test/unit/type/p5.Font.js index ecefcab6bd..1d2daf4bd4 100644 --- a/test/unit/type/p5.Font.js +++ b/test/unit/type/p5.Font.js @@ -1,13 +1,13 @@ import p5 from '../../../src/app.js'; -import {_sanitizeFontName} from '../../../src/type/p5.Font.js'; +import { _sanitizeFontName } from '../../../src/type/p5.Font.js'; suite('p5.Font', function () { var myp5; beforeEach(function () { myp5 = new p5(function (p) { - p.setup = function () { }; - p.draw = function () { }; + p.setup = function () {}; + p.draw = function () {}; }); }); @@ -41,14 +41,14 @@ suite('p5.Font', function () { assert.property(bbox, 'h'); }); - test('fontBounds no NaN (multiline + CENTER)', async () => { - const pFont = await myp5.loadFont(fontFile); - myp5.textAlign(myp5.CENTER, myp5.CENTER); - const b = pFont.fontBounds('Hello,\nWorld!', 50, 50, 24); - expect(b.x).not.toBeNaN(); - expect(b.y).not.toBeNaN(); - expect(b.w).not.toBeNaN(); - expect(b.h).not.toBeNaN(); + test('fontBounds no NaN (multiline + CENTER)', async () => { + const pFont = await myp5.loadFont(fontFile); + myp5.textAlign(myp5.CENTER, myp5.CENTER); + const b = pFont.fontBounds('Hello,\nWorld!', 50, 50, 24); + expect(b.x).not.toBeNaN(); + expect(b.y).not.toBeNaN(); + expect(b.w).not.toBeNaN(); + expect(b.h).not.toBeNaN(); }); suite('textToPoints', () => { @@ -65,7 +65,9 @@ suite('p5.Font', function () { const font = await myp5.loadFont(fontFile); myp5.textSize(50); const pts = font.textToPoints('T', 0, 0); - const simplifiedPts = font.textToPoints('T', 0, 0, { simplifyThreshold: Math.PI * 0.01 }); + const simplifiedPts = font.textToPoints('T', 0, 0, { + simplifyThreshold: Math.PI * 0.01 + }); expect(pts.length).toBeGreaterThan(simplifiedPts.length); }); }); diff --git a/test/unit/utilities/conversion.js b/test/unit/utilities/conversion.js index a4a217cb9e..0a529b04e8 100644 --- a/test/unit/utilities/conversion.js +++ b/test/unit/utilities/conversion.js @@ -1,317 +1,335 @@ import { mockP5, mockP5Prototype } from '../../js/mocks'; import conversion from '../../../src/utilities/conversion'; -suite('Conversion', function() { - beforeAll(function() { +suite('Conversion', function () { + beforeAll(function () { conversion(mockP5, mockP5Prototype); }); - suite('p5.prototype.float', function() { - test('should be a function', function() { + suite('p5.prototype.float', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.float); assert.typeOf(mockP5Prototype.float, 'function'); }); - test('should convert a string to its floating point representation', function() { + test('should convert a string to its floating point representation', function () { const result = mockP5Prototype.float('56.99998'); assert.typeOf(result, 'Number'); assert.strictEqual(result, 56.99998); }); - test('should return NaN for invalid string', function() { + test('should return NaN for invalid string', function () { const result = mockP5Prototype.float('cat'); assert.isNaN(result); }); - test('should return Infinity for Infinity', function() { + test('should return Infinity for Infinity', function () { const result = mockP5Prototype.float(Infinity); assert.strictEqual(result, Infinity); }); - test('should return -Infinity for -Infinity', function() { + test('should return -Infinity for -Infinity', function () { const result = mockP5Prototype.float(-Infinity); assert.strictEqual(result, -Infinity); }); - test('should return array of floating points and Nan', function() { + test('should return array of floating points and Nan', function () { const result = mockP5Prototype.float(['1', '2.0', '3.1', 'giraffe']); assert.typeOf(result, 'Array'); assert.deepEqual(result, [1, 2.0, 3.1, NaN]); }); }); - suite('p5.prototype.int', function() { - test('should be a function', function() { + suite('p5.prototype.int', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.int); assert.typeOf(mockP5Prototype.int, 'function'); }); - test('should convert false to its integer representation i.e. 0', function() { + test('should convert false to its integer representation i.e. 0', function () { const result = mockP5Prototype.int(false); assert.typeOf(result, 'Number'); assert.strictEqual(result, 0); }); - test('should convert true to its integer representation i.e. 1', function() { + test('should convert true to its integer representation i.e. 1', function () { const result = mockP5Prototype.int(true); assert.strictEqual(result, 1); }); - test('should convert a string to its integer representation', function() { + test('should convert a string to its integer representation', function () { const result = mockP5Prototype.int('1001'); assert.strictEqual(result, 1001); }); - test('should return NaN for invalid string', function() { + test('should return NaN for invalid string', function () { const result = mockP5Prototype.int('cat'); assert.isNaN(result); }); - test('should return Infinity for Infinity', function() { + test('should return Infinity for Infinity', function () { const result = mockP5Prototype.int(Infinity); assert.strictEqual(result, Infinity); }); - test('should return -Infinity for -Infinity', function() { + test('should return -Infinity for -Infinity', function () { const result = mockP5Prototype.int(-Infinity); assert.strictEqual(result, -Infinity); }); - test('should convert float to its integer representation', function() { + test('should convert float to its integer representation', function () { const result = mockP5Prototype.int('-1001.9'); assert.strictEqual(result, -1001); }); - test('should return array of integers and NaN', function() { - const result = mockP5Prototype.int(['1', '2.3', '-3.5', 'giraffe', false, 4.7]); + test('should return array of integers and NaN', function () { + const result = mockP5Prototype.int([ + '1', + '2.3', + '-3.5', + 'giraffe', + false, + 4.7 + ]); assert.typeOf(result, 'Array'); assert.deepEqual(result, [1, 2, -3, NaN, 0, 4]); }); }); - suite('p5.prototype.str', function() { - test('should be a function', function() { + suite('p5.prototype.str', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.str); assert.typeOf(mockP5Prototype.str, 'function'); }); - test('should convert false to string', function() { + test('should convert false to string', function () { const result = mockP5Prototype.str(false); assert.typeOf(result, 'String'); assert.strictEqual(result, 'false'); }); - test('should convert true to string', function() { + test('should convert true to string', function () { const result = mockP5Prototype.str(true); assert.strictEqual(result, 'true'); }); - test('should convert a number to string', function() { + test('should convert a number to string', function () { const result = mockP5Prototype.str(45); assert.strictEqual(result, '45'); }); - test('should return array of strings', function() { + test('should return array of strings', function () { const result = mockP5Prototype.str([1, 2.3, true, -4.5]); assert.typeOf(result, 'Array'); assert.deepEqual(result, ['1', '2.3', 'true', '-4.5']); }); }); - suite('p5.prototype.boolean', function() { - test('should be a function', function() { + suite('p5.prototype.boolean', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.boolean); assert.typeOf(mockP5Prototype.boolean, 'function'); }); - test('should convert 1 to true', function() { + test('should convert 1 to true', function () { const result = mockP5Prototype.boolean(1); assert.strictEqual(result, true); }); - test('should convert a number to true', function() { + test('should convert a number to true', function () { const result = mockP5Prototype.boolean(154); assert.strictEqual(result, true); }); - test('should return true for Infinity', function() { + test('should return true for Infinity', function () { const result = mockP5Prototype.boolean(Infinity); assert.strictEqual(result, true); }); - test('should convert 0 to false', function() { + test('should convert 0 to false', function () { const result = mockP5Prototype.boolean(0); assert.strictEqual(result, false); }); - test('should convert a string to false', function() { + test('should convert a string to false', function () { const result = mockP5Prototype.boolean('1'); assert.strictEqual(result, false); }); - test('should convert a string to false', function() { + test('should convert a string to false', function () { const result = mockP5Prototype.boolean('0'); assert.strictEqual(result, false); }); - test('should convert "true" to true', function() { + test('should convert "true" to true', function () { const result = mockP5Prototype.boolean('true'); assert.strictEqual(result, true); }); - test('should return false for empty string', function() { + test('should return false for empty string', function () { const result = mockP5Prototype.boolean(''); assert.strictEqual(result, false); }); - test('should return array of boolean', function() { - const result = mockP5Prototype.boolean([1, true, -4.5, Infinity, 'cat', '23']); + test('should return array of boolean', function () { + const result = mockP5Prototype.boolean([ + 1, + true, + -4.5, + Infinity, + 'cat', + '23' + ]); assert.typeOf(result, 'Array'); assert.deepEqual(result, [true, true, true, true, false, false]); }); }); - suite('p5.prototype.byte', function() { - test('should be a function', function() { + suite('p5.prototype.byte', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.byte); assert.typeOf(mockP5Prototype.byte, 'function'); }); - test('should return 127 for 127', function() { + test('should return 127 for 127', function () { const result = mockP5Prototype.byte(127); assert.strictEqual(result, 127); }); - test('should return -128 for 128', function() { + test('should return -128 for 128', function () { const result = mockP5Prototype.byte(128); assert.strictEqual(result, -128); }); - test('should return 23 for 23.4', function() { + test('should return 23 for 23.4', function () { const result = mockP5Prototype.byte(23.4); assert.strictEqual(result, 23); }); - test('should return 1 for true', function() { + test('should return 1 for true', function () { const result = mockP5Prototype.byte(true); assert.strictEqual(result, 1); }); - test('should return 23 for "23.4"', function() { + test('should return 23 for "23.4"', function () { const result = mockP5Prototype.byte('23.4'); assert.strictEqual(result, 23); }); - test('should return NaN for invalid string', function() { + test('should return NaN for invalid string', function () { const result = mockP5Prototype.byte('cat'); assert.isNaN(result); }); - test('should return array', function() { + test('should return array', function () { const result = mockP5Prototype.byte([0, 255, '100']); assert.typeOf(result, 'Array'); assert.deepEqual(result, [0, -1, 100]); }); }); - suite('p5.prototype.char', function() { - test('should be a function', function() { + suite('p5.prototype.char', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.char); assert.typeOf(mockP5Prototype.char, 'function'); }); - test('should return the char representation of the number', function() { + test('should return the char representation of the number', function () { const result = mockP5Prototype.char(65); assert.typeOf(result, 'String'); assert.strictEqual(result, 'A'); }); - test('should return the char representation of the string', function() { + test('should return the char representation of the string', function () { const result = mockP5Prototype.char('65'); assert.strictEqual(result, 'A'); }); - test('should return array', function() { + test('should return array', function () { const result = mockP5Prototype.char([65, 66, '67']); assert.typeOf(result, 'Array'); assert.deepEqual(result, ['A', 'B', 'C']); }); }); - suite('p5.prototype.unchar', function() { - test('should be a function', function() { + suite('p5.prototype.unchar', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.unchar); assert.typeOf(mockP5Prototype.unchar, 'function'); }); - test('should return the integer representation of char', function() { + test('should return the integer representation of char', function () { const result = mockP5Prototype.unchar('A'); assert.typeOf(result, 'Number'); assert.strictEqual(result, 65); }); - test('should return array of numbers', function() { + test('should return array of numbers', function () { const result = mockP5Prototype.unchar(['A', 'B', 'C']); assert.typeOf(result, 'Array'); assert.deepEqual(result, [65, 66, 67]); }); }); - suite('p5.prototype.hex', function() { - test('should be a function', function() { + suite('p5.prototype.hex', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.hex); assert.typeOf(mockP5Prototype.hex, 'function'); }); - test('should return the hex representation of the number', function() { + test('should return the hex representation of the number', function () { const result = mockP5Prototype.hex(65); assert.typeOf(result, 'String'); assert.strictEqual(result, '00000041'); }); - test('should return FFFFFFFF for Infinity', function() { + test('should return FFFFFFFF for Infinity', function () { const result = mockP5Prototype.hex(Infinity); assert.typeOf(result, 'String'); assert.strictEqual(result, 'FFFFFFFF'); }); - test('should return 00000000 for -Infinity', function() { + test('should return 00000000 for -Infinity', function () { const result = mockP5Prototype.hex(-Infinity); assert.typeOf(result, 'String'); assert.strictEqual(result, '00000000'); }); - test('should return array', function() { + test('should return array', function () { const result = mockP5Prototype.hex([65, 66, 67]); assert.typeOf(result, 'Array'); assert.deepEqual(result, ['00000041', '00000042', '00000043']); }); }); - suite('p5.prototype.unhex', function() { - test('should be a function', function() { + suite('p5.prototype.unhex', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.unhex); assert.typeOf(mockP5Prototype.unchar, 'function'); }); - test('should return the integer representation of hex', function() { + test('should return the integer representation of hex', function () { const result = mockP5Prototype.unhex('00000041'); assert.typeOf(result, 'Number'); assert.strictEqual(result, 65); }); - test('should return the NaN for empty string', function() { + test('should return the NaN for empty string', function () { const result = mockP5Prototype.unhex(''); assert.isNaN(result); }); - test('should return the NaN for invalid hex string', function() { + test('should return the NaN for invalid hex string', function () { const result = mockP5Prototype.unhex('lorem'); assert.isNaN(result); }); - test('should return array of numbers', function() { - const result = mockP5Prototype.unhex(['00000041', '00000042', '00000043']); + test('should return array of numbers', function () { + const result = mockP5Prototype.unhex([ + '00000041', + '00000042', + '00000043' + ]); assert.typeOf(result, 'Array'); assert.deepEqual(result, [65, 66, 67]); }); diff --git a/test/unit/utilities/time_date.js b/test/unit/utilities/time_date.js index 189d7c8e68..777dff1736 100644 --- a/test/unit/utilities/time_date.js +++ b/test/unit/utilities/time_date.js @@ -1,83 +1,83 @@ import { mockP5, mockP5Prototype } from '../../js/mocks'; import timeDate from '../../../src/utilities/time_date'; -suite('time and date', function() { - beforeAll(function() { +suite('time and date', function () { + beforeAll(function () { timeDate(mockP5, mockP5Prototype); }); - suite('p5.prototype.year', function() { - test('should be a function', function() { + suite('p5.prototype.year', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.year); assert.typeOf(mockP5Prototype.year, 'function'); }); - test('should return this year', function() { + test('should return this year', function () { const result = mockP5Prototype.year(); var jsYear = new Date().getFullYear(); assert.equal(result, jsYear); }); }); - suite('p5.prototype.day', function() { - test('should be a function', function() { + suite('p5.prototype.day', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.day); assert.typeOf(mockP5Prototype.day, 'function'); }); - test('should return todays day', function() { + test('should return todays day', function () { var jsDay = new Date().getDate(); const result = mockP5Prototype.day(); assert.equal(result, jsDay); }); }); - suite('p5.prototype.month', function() { - test('should be a function', function() { + suite('p5.prototype.month', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.month); assert.typeOf(mockP5Prototype.month, 'function'); }); - test("should return today's month", function() { + test("should return today's month", function () { const result = mockP5Prototype.month(); var jsMonth = new Date().getMonth() + 1; assert.equal(result, jsMonth); }); }); - suite('p5.prototype.hour', function() { - test('should be a function', function() { + suite('p5.prototype.hour', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.hour); assert.typeOf(mockP5Prototype.hour, 'function'); }); - test('should return this hour', function() { + test('should return this hour', function () { var jsHour = new Date().getHours(); const result = mockP5Prototype.hour(); assert.equal(result, jsHour); }); }); - suite('p5.prototype.second', function() { - test('should be a function', function() { + suite('p5.prototype.second', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.second); assert.typeOf(mockP5Prototype.second, 'function'); }); - test('should return this second', function() { + test('should return this second', function () { var jsSecond = new Date().getSeconds(); const result = mockP5Prototype.second(); assert.equal(result, jsSecond); //(Math.abs(jsSecond - result), '==', 0, 'everything is ok'); // in my testing, found this might be off by 1 second }); }); - suite('p5.prototype.minute', function() { - test('should be a function', function() { + suite('p5.prototype.minute', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.minute); assert.typeOf(mockP5Prototype.minute, 'function'); }); - test('should return a number that is this minute', function() { + test('should return a number that is this minute', function () { var jsMinute = new Date().getMinutes(); const result = mockP5Prototype.minute(); assert.isNumber(result); @@ -86,28 +86,33 @@ suite('time and date', function() { }); }); - suite('p5.prototype.millis', function() { - test('should be a function', function() { + suite('p5.prototype.millis', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.millis); assert.typeOf(mockP5Prototype.millis, 'function'); }); - test('result should be a number', function() { + test('result should be a number', function () { assert.isNumber(mockP5Prototype.millis()); }); // TODO: need to move internal state to module - test.todo('result should be greater than running time', function() { + test.todo('result should be greater than running time', function () { var runningTime = 50; var init_date = window.performance.now(); // wait :\ while (window.performance.now() - init_date <= runningTime) { /* no-op */ } - assert.operator(mockP5Prototype.millis(), '>', runningTime, 'everything is ok'); + assert.operator( + mockP5Prototype.millis(), + '>', + runningTime, + 'everything is ok' + ); }); - test.todo('result should be > newResult', function() { + test.todo('result should be > newResult', function () { var runningTime = 50; var init_date = Date.now(); const result = mockP5Prototype.millis(); diff --git a/test/unit/utilities/utility_functions.js b/test/unit/utilities/utility_functions.js index 3b634ca496..cfdd1bb7a7 100644 --- a/test/unit/utilities/utility_functions.js +++ b/test/unit/utilities/utility_functions.js @@ -2,132 +2,132 @@ import { mockP5, mockP5Prototype } from '../../js/mocks'; import stringFunctions from '../../../src/utilities/utility_functions'; import random from '../../../src/math/random'; -suite('String functions', function() { - beforeAll(function() { +suite('String functions', function () { + beforeAll(function () { stringFunctions(mockP5, mockP5Prototype); random(mockP5, mockP5Prototype); }); - suite('p5.prototype.nf', function() { - test('should be a function', function() { + suite('p5.prototype.nf', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.nf); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 1234; const result = mockP5Prototype.nf(num, 3); assert.equal(result, '1234'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 1234; const result = mockP5Prototype.nf(num, 5); assert.equal(result, '01234'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 1234; const result = mockP5Prototype.nf(num, 3, 3); assert.equal(result, '1234.000'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 3.141516; const result = mockP5Prototype.nf(num, '2'); // automatic conversion? assert.equal(result, '03.141516'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 3.141516; const result = mockP5Prototype.nf(num, '2', '2'); // automatic conversion? assert.equal(result, '03.14'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 3.141516e-2; const result = mockP5Prototype.nf(num, '3', '4'); // automatic conversion? assert.equal(result, '000.0314'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 3.141516e7; const result = mockP5Prototype.nf(num, '3', '4'); // automatic conversion? assert.equal(result, '31415160.0000'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 123.45; const result = mockP5Prototype.nf(num, 3, 0); assert.equal(result, '123'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = -123; const result = mockP5Prototype.nf(num, 5); assert.equal(result, '-00123'); }); }); - suite('p5.prototype.nfc', function() { - test('should be a function', function() { + suite('p5.prototype.nfc', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.nfc); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 32000; const result = mockP5Prototype.nfc(num, 3); assert.equal(result, '32,000.000'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 32000; const result = mockP5Prototype.nfc(num, '3'); // automatic conversion? assert.equal(result, '32,000.000'); }); }); - suite('p5.prototype.nfp', function() { - test('should be a function', function() { + suite('p5.prototype.nfp', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.nfp); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = -32000; const result = mockP5Prototype.nfp(num, 3); assert.equal(result, '-32000'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 32000; const result = mockP5Prototype.nfp(num, 3); // automatic conversion? assert.equal(result, '+32000'); }); }); - suite('p5.prototype.nfs', function() { - test('should be a function', function() { + suite('p5.prototype.nfs', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.nfs); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = -32000; const result = mockP5Prototype.nfs(num, 3); assert.equal(result, '-32000'); }); - test('should return correct string', function() { + test('should return correct string', function () { var num = 32000; const result = mockP5Prototype.nfs(num, 3); // automatic conversion? assert.equal(result, ' 32000'); }); }); - suite('p5.prototype.splitTokens', function() { - test('should be a function', function() { + suite('p5.prototype.splitTokens', function () { + test('should be a function', function () { assert.ok(mockP5Prototype.splitTokens); }); - test('should return correct index of match strings', function() { + test('should return correct index of match strings', function () { var str = 'parsely, sage, rosemary, thyme'; var regexp = ','; const result = mockP5Prototype.splitTokens(str, regexp); @@ -135,8 +135,8 @@ suite('String functions', function() { }); }); - suite('p5.prototype.shuffle', function() { - test('should contain all the elements of the original array', function() { + suite('p5.prototype.shuffle', function () { + test('should contain all the elements of the original array', function () { let regularArr = ['ABC', 'def', {}, Math.PI * 2, Math.E]; let newArr = mockP5Prototype.shuffle(regularArr); let flag = true; diff --git a/test/unit/visual/cases/noise.js b/test/unit/visual/cases/noise.js index 11e64ef06f..e6e35e7387 100644 --- a/test/unit/visual/cases/noise.js +++ b/test/unit/visual/cases/noise.js @@ -1,7 +1,7 @@ import { visualSuite, visualTest } from '../visualTest'; -visualSuite('Noise', function() { - visualTest('Drawn as values', function(p5, screenshot) { +visualSuite('Noise', function () { + visualTest('Drawn as values', function (p5, screenshot) { p5.createCanvas(50, 50); p5.background(255); p5.strokeWeight(2); @@ -15,17 +15,24 @@ visualSuite('Noise', function() { screenshot(); }); - visualTest('Drawn in a shader', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseFilterShader().modify(() => { - p5.getColor(inputs => { - const value = p5.clamp(p5.noise(inputs.texCoord * 4) * 2, 0, 1); - return [value, value, value, 1]; - }); - }, { p5 }); - p5.filter(shader); - screenshot(); - }, { skip: true }); + visualTest( + 'Drawn in a shader', + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + const shader = p5.baseFilterShader().modify( + () => { + p5.getColor(inputs => { + const value = p5.clamp(p5.noise(inputs.texCoord * 4) * 2, 0, 1); + return [value, value, value, 1]; + }); + }, + { p5 } + ); + p5.filter(shader); + screenshot(); + }, + { skip: true } + ); // TODO(dave): Figure out why the noise shader looks different on CI and // re-enable this test }); diff --git a/test/unit/visual/cases/shape_modes.js b/test/unit/visual/cases/shape_modes.js index 692ab48100..7c7506bcb5 100644 --- a/test/unit/visual/cases/shape_modes.js +++ b/test/unit/visual/cases/shape_modes.js @@ -13,8 +13,8 @@ function shapeCorners(p5, shape, mode, x1, y1, x2, y2) { // Adjust coordinates for testing modes other than CORNERS if (mode === p5.CORNER) { // Find top left corner - let x = p5.min(x1, x2); // x - let y = p5.min(y1, y2); // y + let x = p5.min(x1, x2); // x + let y = p5.min(y1, y2); // y // Calculate width and height // Don't use abs(), so we get negative values as well let w = x2 - x1; // w @@ -22,10 +22,17 @@ function shapeCorners(p5, shape, mode, x1, y1, x2, y2) { // With mode CORNER, rects with negative widths/heights result in mirrored/flipped rendering // In this case, adjust position so the rect is in line with the other cases if (shape === 'rect') { - if (w < 0) { x += (-w); } // Move right - if (h < 0) { y += (-h); } // Move down + if (w < 0) { + x += -w; + } // Move right + if (h < 0) { + y += -h; + } // Move down } - x1 = x; y1 = y; x2 = w; y2 = h; + x1 = x; + y1 = y; + x2 = w; + y2 = h; } else if (mode === p5.CENTER) { // Find center let x = (x2 + x1) / 2; // x @@ -34,7 +41,10 @@ function shapeCorners(p5, shape, mode, x1, y1, x2, y2) { // Don't use abs(), so we get negative values as well let w = x2 - x1; let h = y2 - y1; - x1 = x; y1 = y; x2 = w; y2 = h; + x1 = x; + y1 = y; + x2 = w; + y2 = h; } else if (mode === p5.RADIUS) { // Find Center let x = (x2 + x1) / 2; // x @@ -43,7 +53,10 @@ function shapeCorners(p5, shape, mode, x1, y1, x2, y2) { // Don't use abs(), so we get negative values as well let r1 = (x2 - x1) / 2; // r1; let r2 = (y2 - y1) / 2; // r2 - x1 = x; y1 = y; x2 = r1; y2 = r2; + x1 = x; + y1 = y; + x2 = r1; + y2 = r2; } if (shape === 'ellipse') { @@ -63,8 +76,7 @@ function shapeCorners(p5, shape, mode, x1, y1, x2, y2) { } } - -visualSuite('Shape Modes', function() { +visualSuite('Shape Modes', function () { /* Comprehensive test for rendering ellipse(), arc(), and rect() with the different ellipseMode() / rectMode() values: CORNERS, CORNER, CENTER, RADIUS. @@ -74,18 +86,17 @@ visualSuite('Shape Modes', function() { */ // Shapes to test - const SHAPES = [ 'ellipse', 'arc', 'rect' ]; + const SHAPES = ['ellipse', 'arc', 'rect']; // Modes to test (used with ellipseMode or rectMode, according to shape) - const MODES = [ 'CORNERS', 'CORNER', 'CENTER', 'RADIUS' ]; + const MODES = ['CORNERS', 'CORNER', 'CENTER', 'RADIUS']; for (let shape of SHAPES) { - visualSuite(`Shape ${shape}`, function() { - + visualSuite(`Shape ${shape}`, function () { for (let mode of MODES) { - visualTest(`Mode ${mode}`, function(p5, screenshot) { + visualTest(`Mode ${mode}`, function (p5, screenshot) { p5.createCanvas(60, 125); - p5.translate(p5.width/2, p5.height/2); + p5.translate(p5.width / 2, p5.height / 2); // Make the following calls to shapeCorners shorter // by omitting p5, shape and mode parameters @@ -95,87 +106,84 @@ visualSuite('Shape Modes', function() { // Quadrant I (Bottom Right) // P1 P2 - _shapeCorners( 5, 5, 25, 15); // P1 Top Left, P2 Bottom Right - _shapeCorners( 5, 20, 25, 30); // P1 Bottom Left, P2 Top Right - _shapeCorners(25, 45, 5, 35); // P1 Bottom Right, P2 Top Left - _shapeCorners(25, 50, 5, 60); // P1 Top Right, P2 Bottom Left + _shapeCorners(5, 5, 25, 15); // P1 Top Left, P2 Bottom Right + _shapeCorners(5, 20, 25, 30); // P1 Bottom Left, P2 Top Right + _shapeCorners(25, 45, 5, 35); // P1 Bottom Right, P2 Top Left + _shapeCorners(25, 50, 5, 60); // P1 Top Right, P2 Bottom Left // Quadrant II (Bottom Left) - _shapeCorners(-25, 5, -5, 15); - _shapeCorners(-25, 20, -5, 30); - _shapeCorners( -5, 45, -25, 35); - _shapeCorners( -5, 50, -25, 60); + _shapeCorners(-25, 5, -5, 15); + _shapeCorners(-25, 20, -5, 30); + _shapeCorners(-5, 45, -25, 35); + _shapeCorners(-5, 50, -25, 60); // Quadrant III (Top Left) - _shapeCorners(-25, -60, -5, -50); - _shapeCorners(-25, -35, -5, -45); - _shapeCorners( -5, -20, -25, -30); - _shapeCorners( -5, -15, -25, -5); + _shapeCorners(-25, -60, -5, -50); + _shapeCorners(-25, -35, -5, -45); + _shapeCorners(-5, -20, -25, -30); + _shapeCorners(-5, -15, -25, -5); // Quadrant IV (Top Right) - _shapeCorners( 5, -60, 25, -50); - _shapeCorners( 5, -35, 25, -45); - _shapeCorners(25, -20, 5, -30); - _shapeCorners(25, -15, 5, -5); + _shapeCorners(5, -60, 25, -50); + _shapeCorners(5, -35, 25, -45); + _shapeCorners(25, -20, 5, -30); + _shapeCorners(25, -15, 5, -5); screenshot(); }); // End of: visualTest } // End of: MODES loop - }); // End of: visualSuite } // End of: SHAPES loop - /* An extra test suite specific to shape mode CORNER and negative dimensions. For rect, negative widths/heights result in flipped rendering (horizontally/vertically) For ellipse and arc, using negative widths/heights has no effect (the absolute value is used) */ - visualSuite('Negative dimensions', function() { + visualSuite('Negative dimensions', function () { // Negative widths/height result in flipped rects. - visualTest('rect', function(p5, screenshot) { + visualTest('rect', function (p5, screenshot) { p5.createCanvas(50, 50); - p5.translate(p5.width/2, p5.height/2); + p5.translate(p5.width / 2, p5.height / 2); p5.rectMode(p5.CORNER); - p5.rect(0, 0, 20, 10); + p5.rect(0, 0, 20, 10); p5.fill('red'); - p5.rect(0, 0, -20, 10); + p5.rect(0, 0, -20, 10); p5.fill('green'); - p5.rect(0, 0, 20, -10); + p5.rect(0, 0, 20, -10); p5.fill('blue'); p5.rect(0, 0, -20, -10); screenshot(); }); // Since negative widths/heights are used with their absolute value, // ellipses are drawn on top of each other, blue one last - visualTest('ellipse', function(p5, screenshot) { + visualTest('ellipse', function (p5, screenshot) { p5.createCanvas(50, 50); - p5.translate(p5.width/2, p5.height/2); + p5.translate(p5.width / 2, p5.height / 2); p5.ellipseMode(p5.CORNER); - p5.ellipse(0, 0, 20, 10); + p5.ellipse(0, 0, 20, 10); p5.fill('red'); - p5.ellipse(0, 0, -20, 10); + p5.ellipse(0, 0, -20, 10); p5.fill('green'); - p5.ellipse(0, 0, 20, -10); + p5.ellipse(0, 0, 20, -10); p5.fill('blue'); p5.ellipse(0, 0, -20, -10); screenshot(); }); // Since negative widths/heights are used with their absolute value, // arcs are drawn on top of each other, blue one last. - visualTest('arc', function(p5, screenshot) { + visualTest('arc', function (p5, screenshot) { p5.createCanvas(50, 50); - p5.translate(p5.width/2, p5.height/2); + p5.translate(p5.width / 2, p5.height / 2); p5.ellipseMode(p5.CORNER); - p5.arc(0, 0, 20, 10, 0, p5.PI + p5.HALF_PI); + p5.arc(0, 0, 20, 10, 0, p5.PI + p5.HALF_PI); p5.fill('red'); - p5.arc(0, 0, -20, 10, 0, p5.PI + p5.HALF_PI); + p5.arc(0, 0, -20, 10, 0, p5.PI + p5.HALF_PI); p5.fill('green'); - p5.arc(0, 0, 20, -10, 0, p5.PI + p5.HALF_PI); + p5.arc(0, 0, 20, -10, 0, p5.PI + p5.HALF_PI); p5.fill('blue'); p5.arc(0, 0, -20, -10, 0, p5.PI + p5.HALF_PI); screenshot(); }); }); - }); diff --git a/test/unit/visual/cases/shapes.js b/test/unit/visual/cases/shapes.js index 2c245dbec2..b3fc2dede8 100644 --- a/test/unit/visual/cases/shapes.js +++ b/test/unit/visual/cases/shapes.js @@ -1,8 +1,8 @@ import { visualSuite, visualTest } from '../visualTest'; -visualSuite('Shape drawing', function() { +visualSuite('Shape drawing', function () { for (const mode of ['2D', 'WebGL']) { - visualSuite(`${mode} mode`, function() { + visualSuite(`${mode} mode`, function () { const setup = p5 => { p5.createCanvas(50, 50, mode === '2D' ? p5.P2D : p5.WEBGL); if (mode !== '2D') { @@ -13,7 +13,7 @@ visualSuite('Shape drawing', function() { p5.stroke(0); }; - visualTest('Drawing polylines', function(p5, screenshot) { + visualTest('Drawing polylines', function (p5, screenshot) { setup(p5); p5.beginShape(); p5.vertex(10, 10); @@ -25,7 +25,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing with contours', function(p5, screenshot) { + visualTest('Drawing with contours', function (p5, screenshot) { setup(p5); const vertexCircle = (x, y, r, direction) => { @@ -52,19 +52,19 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing triangle fans', function(p5, screenshot) { + visualTest('Drawing triangle fans', function (p5, screenshot) { setup(p5); p5.beginShape(p5.TRIANGLE_FAN); p5.vertex(25, 25); for (let i = 0; i <= 12; i++) { const angle = p5.map(i, 0, 12, 0, p5.TWO_PI); - p5.vertex(25 + 10*p5.cos(angle), 25 + 10*p5.sin(angle)); + p5.vertex(25 + 10 * p5.cos(angle), 25 + 10 * p5.sin(angle)); } p5.endShape(); screenshot(); }); - visualTest('Drawing triangle strips', function(p5, screenshot) { + visualTest('Drawing triangle strips', function (p5, screenshot) { setup(p5); p5.beginShape(p5.TRIANGLE_STRIP); p5.vertex(10, 10); @@ -77,7 +77,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing quad strips', function(p5, screenshot) { + visualTest('Drawing quad strips', function (p5, screenshot) { setup(p5); p5.beginShape(p5.QUAD_STRIP); p5.vertex(10, 10); @@ -90,7 +90,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing closed polylines', function(p5, screenshot) { + visualTest('Drawing closed polylines', function (p5, screenshot) { setup(p5); p5.beginShape(); p5.vertex(10, 10); @@ -102,7 +102,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing with curves', function(p5, screenshot) { + visualTest('Drawing with curves', function (p5, screenshot) { setup(p5); p5.beginShape(); p5.splineVertex(10, 10); @@ -114,31 +114,37 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing with curves in the middle of other shapes', function(p5, screenshot) { - setup(p5); - p5.beginShape(); - p5.vertex(10, 10); - p5.vertex(40, 10); - p5.splineVertex(40, 40); - p5.splineVertex(10, 40); - p5.endShape(p5.CLOSE); - screenshot(); - }); + visualTest( + 'Drawing with curves in the middle of other shapes', + function (p5, screenshot) { + setup(p5); + p5.beginShape(); + p5.vertex(10, 10); + p5.vertex(40, 10); + p5.splineVertex(40, 40); + p5.splineVertex(10, 40); + p5.endShape(p5.CLOSE); + screenshot(); + } + ); - visualTest('Drawing with curves with hidden ends', function(p5, screenshot) { - setup(p5); - p5.beginShape(); - p5.splineProperty('ends', p5.EXCLUDE); - p5.splineVertex(10, 10); - p5.splineVertex(15, 40); - p5.splineVertex(40, 35); - p5.splineVertex(25, 15); - p5.splineVertex(15, 25); - p5.endShape(); - screenshot(); - }); + visualTest( + 'Drawing with curves with hidden ends', + function (p5, screenshot) { + setup(p5); + p5.beginShape(); + p5.splineProperty('ends', p5.EXCLUDE); + p5.splineVertex(10, 10); + p5.splineVertex(15, 40); + p5.splineVertex(40, 35); + p5.splineVertex(25, 15); + p5.splineVertex(15, 25); + p5.endShape(); + screenshot(); + } + ); - visualTest('Drawing closed curves', function(p5, screenshot) { + visualTest('Drawing closed curves', function (p5, screenshot) { setup(p5); p5.beginShape(); p5.splineVertex(10, 10); @@ -150,7 +156,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing simple closed curves', function(p5, screenshot) { + visualTest('Drawing simple closed curves', function (p5, screenshot) { setup(p5); p5.beginShape(); p5.splineVertex(10, 10); @@ -160,20 +166,23 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing with curves with tightness', function(p5, screenshot) { - setup(p5); - p5.splineProperty('tightness', -1); - p5.beginShape(); - p5.splineVertex(10, 10); - p5.splineVertex(15, 40); - p5.splineVertex(40, 35); - p5.splineVertex(25, 15); - p5.splineVertex(15, 25); - p5.endShape(); - screenshot(); - }); + visualTest( + 'Drawing with curves with tightness', + function (p5, screenshot) { + setup(p5); + p5.splineProperty('tightness', -1); + p5.beginShape(); + p5.splineVertex(10, 10); + p5.splineVertex(15, 40); + p5.splineVertex(40, 35); + p5.splineVertex(25, 15); + p5.splineVertex(15, 25); + p5.endShape(); + screenshot(); + } + ); - visualTest('Drawing closed curve loops', function(p5, screenshot) { + visualTest('Drawing closed curve loops', function (p5, screenshot) { setup(p5); p5.beginShape(); p5.splineProperty('ends', p5.EXCLUDE); @@ -190,7 +199,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing with cubic beziers', function(p5, screenshot) { + visualTest('Drawing with cubic beziers', function (p5, screenshot) { setup(p5); p5.beginShape(); p5.bezierVertex(10, 10); @@ -206,7 +215,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing with quadratic beziers', function(p5, screenshot) { + visualTest('Drawing with quadratic beziers', function (p5, screenshot) { setup(p5); p5.beginShape(); p5.bezierOrder(2); @@ -224,32 +233,35 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Combining quadratic and cubic beziers', function (p5, screenshot) { - setup(p5); - p5.strokeWeight(5); - p5.beginShape(); - p5.vertex(10, 10); - p5.vertex(30, 10); + visualTest( + 'Combining quadratic and cubic beziers', + function (p5, screenshot) { + setup(p5); + p5.strokeWeight(5); + p5.beginShape(); + p5.vertex(10, 10); + p5.vertex(30, 10); - // Default cubic - p5.bezierVertex(35, 10); - p5.bezierVertex(40, 15); - p5.bezierVertex(40, 20); + // Default cubic + p5.bezierVertex(35, 10); + p5.bezierVertex(40, 15); + p5.bezierVertex(40, 20); - p5.vertex(40, 30); + p5.vertex(40, 30); - p5.bezierOrder(2); - p5.bezierVertex(40, 40); - p5.bezierVertex(30, 40); + p5.bezierOrder(2); + p5.bezierVertex(40, 40); + p5.bezierVertex(30, 40); - p5.vertex(10, 40); + p5.vertex(10, 40); - p5.endShape(p5.CLOSE); + p5.endShape(p5.CLOSE); - screenshot(); - }); + screenshot(); + } + ); - visualTest('Drawing with points', function(p5, screenshot) { + visualTest('Drawing with points', function (p5, screenshot) { setup(p5); p5.strokeWeight(5); p5.beginShape(p5.POINTS); @@ -262,7 +274,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing with lines', function(p5, screenshot) { + visualTest('Drawing with lines', function (p5, screenshot) { setup(p5); p5.beginShape(p5.LINES); p5.vertex(10, 10); @@ -273,7 +285,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing with triangles', function(p5, screenshot) { + visualTest('Drawing with triangles', function (p5, screenshot) { setup(p5); p5.beginShape(p5.TRIANGLES); p5.vertex(10, 10); @@ -286,7 +298,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing with quads', function(p5, screenshot) { + visualTest('Drawing with quads', function (p5, screenshot) { setup(p5); p5.beginShape(p5.QUADS); p5.vertex(10, 10); @@ -301,74 +313,83 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Drawing rounded rectangles', function(p5, screenshot) { + visualTest('Drawing rounded rectangles', function (p5, screenshot) { setup(p5); p5.rect(5, 5, 40, 20, 5); p5.rect(5, 30, 40, 15, 2, 4, 6, 8); screenshot(); }); - visualTest('Drawing with a single closed contour', function(p5, screenshot) { - setup(p5); - p5.beginShape(); - p5.vertex(10, 10); - p5.vertex(40, 10); - p5.vertex(40, 40); - p5.vertex(10, 40); - - p5.beginContour(); - p5.vertex(20, 20); - p5.vertex(20, 30); - p5.vertex(30, 30); - p5.vertex(30, 20); - p5.endContour(p5.CLOSE); + visualTest( + 'Drawing with a single closed contour', + function (p5, screenshot) { + setup(p5); + p5.beginShape(); + p5.vertex(10, 10); + p5.vertex(40, 10); + p5.vertex(40, 40); + p5.vertex(10, 40); - p5.endShape(p5.CLOSE); - screenshot(); - }); + p5.beginContour(); + p5.vertex(20, 20); + p5.vertex(20, 30); + p5.vertex(30, 30); + p5.vertex(30, 20); + p5.endContour(p5.CLOSE); - visualTest('Drawing with a single unclosed contour', function(p5, screenshot) { - setup(p5); - p5.beginShape(); - p5.vertex(10, 10); - p5.vertex(40, 10); - p5.vertex(40, 40); - p5.vertex(10, 40); + p5.endShape(p5.CLOSE); + screenshot(); + } + ); - p5.beginContour(); - p5.vertex(20, 20); - p5.vertex(20, 30); - p5.vertex(30, 30); - p5.vertex(30, 20); - p5.endContour(); + visualTest( + 'Drawing with a single unclosed contour', + function (p5, screenshot) { + setup(p5); + p5.beginShape(); + p5.vertex(10, 10); + p5.vertex(40, 10); + p5.vertex(40, 40); + p5.vertex(10, 40); - p5.endShape(p5.CLOSE); - screenshot(); - }); + p5.beginContour(); + p5.vertex(20, 20); + p5.vertex(20, 30); + p5.vertex(30, 30); + p5.vertex(30, 20); + p5.endContour(); - visualTest('Drawing with every subshape in a contour', function(p5, screenshot) { - setup(p5); - p5.beginShape(); - p5.beginContour(); - p5.vertex(10, 10); - p5.vertex(40, 10); - p5.vertex(40, 40); - p5.vertex(10, 40); - p5.endContour(p5.CLOSE); + p5.endShape(p5.CLOSE); + screenshot(); + } + ); - p5.beginContour(); - p5.vertex(20, 20); - p5.vertex(20, 30); - p5.vertex(30, 30); - p5.vertex(30, 20); - p5.endContour(p5.CLOSE); + visualTest( + 'Drawing with every subshape in a contour', + function (p5, screenshot) { + setup(p5); + p5.beginShape(); + p5.beginContour(); + p5.vertex(10, 10); + p5.vertex(40, 10); + p5.vertex(40, 40); + p5.vertex(10, 40); + p5.endContour(p5.CLOSE); + + p5.beginContour(); + p5.vertex(20, 20); + p5.vertex(20, 30); + p5.vertex(30, 30); + p5.vertex(30, 20); + p5.endContour(p5.CLOSE); - p5.endShape(); - screenshot(); - }); + p5.endShape(); + screenshot(); + } + ); if (mode === 'WebGL') { - visualTest('3D vertex coordinates', function(p5, screenshot) { + visualTest('3D vertex coordinates', function (p5, screenshot) { setup(p5); p5.beginShape(p5.QUAD_STRIP); @@ -381,7 +402,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('3D quadratic coordinates', function(p5, screenshot) { + visualTest('3D quadratic coordinates', function (p5, screenshot) { setup(p5); p5.beginShape(); @@ -395,7 +416,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('3D cubic coordinates', function(p5, screenshot) { + visualTest('3D cubic coordinates', function (p5, screenshot) { setup(p5); p5.beginShape(); @@ -409,7 +430,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Texture coordinates', async function(p5, screenshot) { + visualTest('Texture coordinates', async function (p5, screenshot) { const tex = await p5.loadImage('test/unit/assets/cat.jpg'); setup(p5); p5.texture(tex); @@ -423,22 +444,25 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Normalized texture coordinates', async function(p5, screenshot) { - const tex = await p5.loadImage('test/unit/assets/cat.jpg'); - setup(p5); - p5.texture(tex); - p5.textureMode(p5.NORMAL); - p5.beginShape(p5.QUAD_STRIP); - p5.vertex(10, 10, 0, 0, 0); - p5.vertex(45, 5, 0, 1, 0); - p5.vertex(15, 35, 0, 0, 1); - p5.vertex(40, 45, 0, 1, 1); - p5.endShape(); - - screenshot(); - }); + visualTest( + 'Normalized texture coordinates', + async function (p5, screenshot) { + const tex = await p5.loadImage('test/unit/assets/cat.jpg'); + setup(p5); + p5.texture(tex); + p5.textureMode(p5.NORMAL); + p5.beginShape(p5.QUAD_STRIP); + p5.vertex(10, 10, 0, 0, 0); + p5.vertex(45, 5, 0, 1, 0); + p5.vertex(15, 35, 0, 0, 1); + p5.vertex(40, 45, 0, 1, 1); + p5.endShape(); + + screenshot(); + } + ); - visualTest('Per-vertex fills', function(p5, screenshot) { + visualTest('Per-vertex fills', function (p5, screenshot) { setup(p5); p5.beginShape(p5.QUAD_STRIP); p5.fill(0); @@ -454,7 +478,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Per-vertex strokes', function(p5, screenshot) { + visualTest('Per-vertex strokes', function (p5, screenshot) { setup(p5); p5.strokeWeight(5); p5.beginShape(p5.QUAD_STRIP); @@ -471,7 +495,7 @@ visualSuite('Shape drawing', function() { screenshot(); }); - visualTest('Per-vertex normals', function(p5, screenshot) { + visualTest('Per-vertex normals', function (p5, screenshot) { setup(p5); p5.normalMaterial(); p5.beginShape(p5.QUAD_STRIP); diff --git a/test/unit/visual/cases/typography.js b/test/unit/visual/cases/typography.js index 10ba96d7bf..02b742fbfa 100644 --- a/test/unit/visual/cases/typography.js +++ b/test/unit/visual/cases/typography.js @@ -1,690 +1,767 @@ import { visualSuite, visualTest } from '../visualTest'; -visualSuite('Typography', function () { - visualSuite('textFont', function () { - visualTest('with the default font', function (p5, screenshot) { - p5.createCanvas(50, 50); - p5.textSize(20); - p5.textAlign(p5.LEFT, p5.TOP); - p5.text('test', 0, 0); - screenshot(); - }); - - visualTest('with the default monospace font', function (p5, screenshot) { - p5.createCanvas(50, 50); - p5.textSize(20); - p5.textFont('monospace'); - p5.textAlign(p5.LEFT, p5.TOP); - p5.text('test', 0, 0); - screenshot(); - }); - - visualTest('with a Google Font URL', async function (p5, screenshot) { - p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&display=swap' - ); - p5.textFont(font); - p5.textAlign(p5.LEFT, p5.TOP); - p5.textSize(35); - p5.text('p5*js', 0, 10, p5.width); - screenshot(); - }); - - visualTest('with a font file', async function (p5, screenshot) { - p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); - p5.textFont(font); - p5.textAlign(p5.LEFT, p5.TOP); - p5.textSize(35); - p5.text('p5*js', 0, 10, p5.width); - screenshot(); - }); - - visualTest('with a font file and special chars', async function (p5, screenshot) { - p5.createCanvas(100, 100, p5.WEBGL); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf', - 'Incönsolata' - ); - p5.textFont(font); - p5.textAlign(p5.CENTER, p5.CENTER); - p5.textSize(35); - p5.text('p5*js', 0, 0); - p5.noFill(); - p5.rectMode(p5.CENTER); - p5.rect(0, 0, p5.fontWidth('p5*js'), p5.textLeading()); - screenshot(); - }); - - visualTest('with a woff font file', async function (p5, screenshot) { - p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'test/unit/assets/Lato-Regular.woff' - ); - p5.textFont(font); - p5.textAlign(p5.LEFT, p5.TOP); - p5.textSize(35); - p5.text('p5*js', 0, 10, p5.width); - screenshot(); - }); - - visualTest('with a directly set font string', async function (p5, screenshot) { - p5.createCanvas(100, 100); - p5.textFont('italic bold 32px serif'); - p5.text('p5*js', 0, 10, p5.width); - screenshot(); - }); - - visualTest('with a font file in WebGL', async function (p5, screenshot) { - p5.createCanvas(100, 100, p5.WEBGL); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); - p5.textFont(font); - p5.textAlign(p5.LEFT, p5.TOP); - p5.textSize(35); - p5.text('p5*js', -p5.width / 2, -p5.height / 2 + 10, p5.width); - screenshot(); - }); - }); +visualSuite( + 'Typography', + function () { + visualSuite('textFont', function () { + visualTest('with the default font', function (p5, screenshot) { + p5.createCanvas(50, 50); + p5.textSize(20); + p5.textAlign(p5.LEFT, p5.TOP); + p5.text('test', 0, 0); + screenshot(); + }); - visualSuite('textWeight', function () { - visualTest('can control non-variable fonts', async function (p5, screenshot) { - p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'https://fonts.googleapis.com/css2?family=Sniglet:wght@400;800&display=swap' - ); + visualTest('with the default monospace font', function (p5, screenshot) { + p5.createCanvas(50, 50); + p5.textSize(20); + p5.textFont('monospace'); + p5.textAlign(p5.LEFT, p5.TOP); + p5.text('test', 0, 0); + screenshot(); + }); - for (const weight of [400, 800]) { - p5.background(255); + visualTest('with a Google Font URL', async function (p5, screenshot) { + p5.createCanvas(100, 100); + const font = await p5.loadFont( + 'https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&display=swap' + ); p5.textFont(font); p5.textAlign(p5.LEFT, p5.TOP); p5.textSize(35); - p5.textWeight(weight); p5.text('p5*js', 0, 10, p5.width); screenshot(); - } - }); + }); - visualTest('can control variable fonts', async function (p5, screenshot) { - p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap' - ); - for (let weight = 400; weight <= 800; weight += 100) { - p5.background(255); + visualTest('with a font file', async function (p5, screenshot) { + p5.createCanvas(100, 100); + const font = await p5.loadFont('test/unit/assets/Inconsolata-Bold.ttf'); p5.textFont(font); p5.textAlign(p5.LEFT, p5.TOP); p5.textSize(35); - p5.textWeight(weight); p5.text('p5*js', 0, 10, p5.width); screenshot(); - } - }); + }); - visualTest('can control variable fonts from files', async function (p5, screenshot) { - p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'test/unit/assets/BricolageGrotesque-Variable.ttf' + visualTest( + 'with a font file and special chars', + async function (p5, screenshot) { + p5.createCanvas(100, 100, p5.WEBGL); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf', + 'Incönsolata' + ); + p5.textFont(font); + p5.textAlign(p5.CENTER, p5.CENTER); + p5.textSize(35); + p5.text('p5*js', 0, 0); + p5.noFill(); + p5.rectMode(p5.CENTER); + p5.rect(0, 0, p5.fontWidth('p5*js'), p5.textLeading()); + screenshot(); + } ); - for (let weight = 400; weight <= 800; weight += 100) { - p5.background(255); + + visualTest('with a woff font file', async function (p5, screenshot) { + p5.createCanvas(100, 100); + const font = await p5.loadFont('test/unit/assets/Lato-Regular.woff'); p5.textFont(font); p5.textAlign(p5.LEFT, p5.TOP); p5.textSize(35); - p5.textWeight(weight); p5.text('p5*js', 0, 10, p5.width); screenshot(); - } - }); + }); - visualTest('can control variable fonts from files in WebGL', async function (p5, screenshot) { - p5.createCanvas(100, 100, p5.WEBGL); - const font = await p5.loadFont( - 'test/unit/assets/BricolageGrotesque-Variable.ttf' + visualTest( + 'with a directly set font string', + async function (p5, screenshot) { + p5.createCanvas(100, 100); + p5.textFont('italic bold 32px serif'); + p5.text('p5*js', 0, 10, p5.width); + screenshot(); + } ); - for (let weight = 400; weight <= 800; weight += 100) { - p5.push(); - p5.background(255); - p5.translate(-p5.width/2, -p5.height/2); + + visualTest('with a font file in WebGL', async function (p5, screenshot) { + p5.createCanvas(100, 100, p5.WEBGL); + const font = await p5.loadFont('test/unit/assets/Inconsolata-Bold.ttf'); p5.textFont(font); p5.textAlign(p5.LEFT, p5.TOP); p5.textSize(35); - p5.textWeight(weight); - p5.text('p5*js', 0, 10, p5.width); - p5.pop(); + p5.text('p5*js', -p5.width / 2, -p5.height / 2 + 10, p5.width); screenshot(); - } + }); }); - }); - - visualSuite('vertical centering', function() { - const fonts = { - Inter: 'https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfMZhrib2Bg-4.ttf', - Raleway: 'https://fonts.gstatic.com/s/raleway/v36/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaooCPNLA3JC9c.ttf', - 'Inknut Antiqua': 'https://fonts.gstatic.com/s/inknutantiqua/v16/Y4GRYax7VC4ot_qNB4nYpBdaKU2_xbj5bBoIYJNf.ttf', - Oswald: 'https://fonts.gstatic.com/s/oswald/v57/TK3_WkUHHAIjg75cFRf3bXL8LICs1_FvgUFoZAaRliE.ttf', - }; - for (const mode of ['2d', 'webgl']) { - for (const fontName in fonts) { - visualTest(`${fontName} in ${mode} mode`, async function (p5, screenshot) { - p5.createCanvas(200, 50, mode === 'webgl' ? p5.WEBGL : p5.P2D); - p5.background(255); - const font = await p5.loadFont(fonts[fontName]); - if (mode === '2d') p5.translate(p5.width/2, p5.height/2); - p5.textFont(font); - p5.stroke('red'); - p5.line(-p5.width/2, 0, p5.width/2, 0); + visualSuite('textWeight', function () { + visualTest( + 'can control non-variable fonts', + async function (p5, screenshot) { + p5.createCanvas(100, 100); + const font = await p5.loadFont( + 'https://fonts.googleapis.com/css2?family=Sniglet:wght@400;800&display=swap' + ); - p5.noStroke(); - p5.fill(0); - p5.textSize(20); - p5.textAlign(p5.CENTER, p5.CENTER); - p5.text('Vertical align', 0, 0); + for (const weight of [400, 800]) { + p5.background(255); + p5.textFont(font); + p5.textAlign(p5.LEFT, p5.TOP); + p5.textSize(35); + p5.textWeight(weight); + p5.text('p5*js', 0, 10, p5.width); + screenshot(); + } + } + ); + + visualTest('can control variable fonts', async function (p5, screenshot) { + p5.createCanvas(100, 100); + const font = await p5.loadFont( + 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap' + ); + for (let weight = 400; weight <= 800; weight += 100) { + p5.background(255); + p5.textFont(font); + p5.textAlign(p5.LEFT, p5.TOP); + p5.textSize(35); + p5.textWeight(weight); + p5.text('p5*js', 0, 10, p5.width); screenshot(); - }); - } - } - }); - - visualSuite('textAlign', function () { - for (const mode of ['2d', 'webgl']) { - visualSuite(`${mode} mode`, () => { - visualTest('all alignments with single word', async function (p5, screenshot) { - const alignments = [ - { alignX: p5.LEFT, alignY: p5.TOP }, - { alignX: p5.CENTER, alignY: p5.TOP }, - { alignX: p5.RIGHT, alignY: p5.TOP }, - { alignX: p5.LEFT, alignY: p5.CENTER }, - { alignX: p5.CENTER, alignY: p5.CENTER }, - { alignX: p5.RIGHT, alignY: p5.CENTER }, - { alignX: p5.LEFT, alignY: p5.BOTTOM }, - { alignX: p5.CENTER, alignY: p5.BOTTOM }, - { alignX: p5.RIGHT, alignY: p5.BOTTOM } - ]; - - p5.createCanvas(300, 300, mode === 'webgl' ? p5.WEBGL : undefined); - if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2); - p5.textSize(60); + } + }); + + visualTest( + 'can control variable fonts from files', + async function (p5, screenshot) { + p5.createCanvas(100, 100); const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' + 'test/unit/assets/BricolageGrotesque-Variable.ttf' ); - p5.textFont(font); - alignments.forEach(alignment => { + for (let weight = 400; weight <= 800; weight += 100) { p5.background(255); - p5.textAlign(alignment.alignX, alignment.alignY); - p5.text('Single Line', p5.width / 2, p5.height / 2); - const bb = p5.textBounds('Single Line', p5.width / 2, p5.height / 2); - p5.push(); - p5.noFill(); - p5.stroke('red'); - p5.rect(bb.x, bb.y, bb.w, bb.h); - p5.pop(); + p5.textFont(font); + p5.textAlign(p5.LEFT, p5.TOP); + p5.textSize(35); + p5.textWeight(weight); + p5.text('p5*js', 0, 10, p5.width); screenshot(); - }); - }); + } + } + ); - visualTest('all alignments with single line', async function (p5, screenshot) { - const alignments = [ - { alignX: p5.LEFT, alignY: p5.TOP }, - { alignX: p5.CENTER, alignY: p5.TOP }, - { alignX: p5.RIGHT, alignY: p5.TOP }, - { alignX: p5.LEFT, alignY: p5.CENTER }, - { alignX: p5.CENTER, alignY: p5.CENTER }, - { alignX: p5.RIGHT, alignY: p5.CENTER }, - { alignX: p5.LEFT, alignY: p5.BOTTOM }, - { alignX: p5.CENTER, alignY: p5.BOTTOM }, - { alignX: p5.RIGHT, alignY: p5.BOTTOM } - ]; - - p5.createCanvas(300, 300, mode === 'webgl' ? p5.WEBGL : undefined); - if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2); - p5.textSize(45); + visualTest( + 'can control variable fonts from files in WebGL', + async function (p5, screenshot) { + p5.createCanvas(100, 100, p5.WEBGL); const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' + 'test/unit/assets/BricolageGrotesque-Variable.ttf' ); - p5.textFont(font); - alignments.forEach(alignment => { - p5.background(255); - p5.textAlign(alignment.alignX, alignment.alignY); - p5.text('Single Line', p5.width / 2, p5.height / 2); - const bb = p5.textBounds('Single Line', p5.width / 2, p5.height / 2); + for (let weight = 400; weight <= 800; weight += 100) { p5.push(); - p5.noFill(); - p5.stroke('red'); - p5.rect(bb.x, bb.y, bb.w, bb.h); + p5.background(255); + p5.translate(-p5.width / 2, -p5.height / 2); + p5.textFont(font); + p5.textAlign(p5.LEFT, p5.TOP); + p5.textSize(35); + p5.textWeight(weight); + p5.text('p5*js', 0, 10, p5.width); p5.pop(); screenshot(); - }); - }); - - visualTest('all alignments with multi-lines and wrap word', - async function (p5, screenshot) { - const alignments = [ - { alignX: p5.LEFT, alignY: p5.TOP }, - { alignX: p5.CENTER, alignY: p5.TOP }, - { alignX: p5.RIGHT, alignY: p5.TOP }, - { alignX: p5.LEFT, alignY: p5.CENTER }, - { alignX: p5.CENTER, alignY: p5.CENTER }, - { alignX: p5.RIGHT, alignY: p5.CENTER }, - { alignX: p5.LEFT, alignY: p5.BOTTOM }, - { alignX: p5.CENTER, alignY: p5.BOTTOM }, - { alignX: p5.RIGHT, alignY: p5.BOTTOM } - ]; - - p5.createCanvas(150, 100, mode === 'webgl' ? p5.WEBGL : undefined); - if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2); - p5.textSize(20); - p5.textWrap(p5.WORD); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); - p5.textFont(font); - - let xPos = 20; - let yPos = 20; - const boxWidth = 100; - const boxHeight = 60; + } + } + ); + }); - alignments.forEach((alignment, i) => { + visualSuite('vertical centering', function () { + const fonts = { + Inter: + 'https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfMZhrib2Bg-4.ttf', + Raleway: + 'https://fonts.gstatic.com/s/raleway/v36/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaooCPNLA3JC9c.ttf', + 'Inknut Antiqua': + 'https://fonts.gstatic.com/s/inknutantiqua/v16/Y4GRYax7VC4ot_qNB4nYpBdaKU2_xbj5bBoIYJNf.ttf', + Oswald: + 'https://fonts.gstatic.com/s/oswald/v57/TK3_WkUHHAIjg75cFRf3bXL8LICs1_FvgUFoZAaRliE.ttf' + }; + for (const mode of ['2d', 'webgl']) { + for (const fontName in fonts) { + visualTest( + `${fontName} in ${mode} mode`, + async function (p5, screenshot) { + p5.createCanvas(200, 50, mode === 'webgl' ? p5.WEBGL : p5.P2D); p5.background(255); - p5.push(); - p5.textAlign(alignment.alignX, alignment.alignY); - - p5.noFill(); - p5.strokeWeight(2); - p5.stroke(200); - p5.rect(xPos, yPos, boxWidth, boxHeight); + const font = await p5.loadFont(fonts[fontName]); + if (mode === '2d') p5.translate(p5.width / 2, p5.height / 2); + p5.textFont(font); - p5.fill(0); - p5.noStroke(); - p5.text( - 'A really long text that should wrap automatically as it reaches the end of the box', - xPos, - yPos, - boxWidth, - boxHeight - ); - const bb = p5.textBounds( - 'A really long text that should wrap automatically as it reaches the end of the box', - xPos, - yPos, - boxWidth, - boxHeight - ); - p5.noFill(); p5.stroke('red'); - p5.rect(bb.x, bb.y, bb.w, bb.h); - p5.pop(); + p5.line(-p5.width / 2, 0, p5.width / 2, 0); + p5.noStroke(); + p5.fill(0); + p5.textSize(20); + p5.textAlign(p5.CENTER, p5.CENTER); + p5.text('Vertical align', 0, 0); screenshot(); - }); - } - ); - - visualTest( - 'all alignments with multi-lines and wrap char', - async function (p5, screenshot) { - const alignments = [ - { alignX: p5.LEFT, alignY: p5.TOP }, - { alignX: p5.CENTER, alignY: p5.TOP }, - { alignX: p5.RIGHT, alignY: p5.TOP }, - { alignX: p5.LEFT, alignY: p5.CENTER }, - { alignX: p5.CENTER, alignY: p5.CENTER }, - { alignX: p5.RIGHT, alignY: p5.CENTER }, - { alignX: p5.LEFT, alignY: p5.BOTTOM }, - { alignX: p5.CENTER, alignY: p5.BOTTOM }, - { alignX: p5.RIGHT, alignY: p5.BOTTOM } - ]; - - p5.createCanvas(150, 100, mode === 'webgl' ? p5.WEBGL : undefined); - if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2); - p5.textSize(19); - p5.textWrap(p5.CHAR); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); - p5.textFont(font); - - let xPos = 20; - let yPos = 20; - const boxWidth = 100; - const boxHeight = 60; - - alignments.forEach((alignment, i) => { - p5.background(255); - p5.push(); - p5.textAlign(alignment.alignX, alignment.alignY); - - p5.noFill(); - p5.strokeWeight(2); - p5.stroke(200); - p5.rect(xPos, yPos, boxWidth, boxHeight); + } + ); + } + } + }); - p5.fill(0); - p5.noStroke(); - p5.text( - 'A really long text that should wrap automatically as it reaches the end of the box', - xPos, - yPos, - boxWidth, - boxHeight + visualSuite('textAlign', function () { + for (const mode of ['2d', 'webgl']) { + visualSuite(`${mode} mode`, () => { + visualTest( + 'all alignments with single word', + async function (p5, screenshot) { + const alignments = [ + { alignX: p5.LEFT, alignY: p5.TOP }, + { alignX: p5.CENTER, alignY: p5.TOP }, + { alignX: p5.RIGHT, alignY: p5.TOP }, + { alignX: p5.LEFT, alignY: p5.CENTER }, + { alignX: p5.CENTER, alignY: p5.CENTER }, + { alignX: p5.RIGHT, alignY: p5.CENTER }, + { alignX: p5.LEFT, alignY: p5.BOTTOM }, + { alignX: p5.CENTER, alignY: p5.BOTTOM }, + { alignX: p5.RIGHT, alignY: p5.BOTTOM } + ]; + + p5.createCanvas( + 300, + 300, + mode === 'webgl' ? p5.WEBGL : undefined ); - const bb = p5.textBounds( - 'A really long text that should wrap automatically as it reaches the end of the box', - xPos, - yPos, - boxWidth, - boxHeight + if (mode === 'webgl') p5.translate(-p5.width / 2, -p5.height / 2); + p5.textSize(60); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' ); - p5.noFill(); - p5.stroke('red'); - p5.rect(bb.x, bb.y, bb.w, bb.h); - p5.pop(); - - screenshot(); - }); - } - ); - - visualTest( - 'all alignments with multi-line manual text', - async function (p5, screenshot) { - const alignments = [ - { alignX: p5.LEFT, alignY: p5.TOP }, - { alignX: p5.CENTER, alignY: p5.TOP }, - { alignX: p5.RIGHT, alignY: p5.TOP }, - { alignX: p5.LEFT, alignY: p5.CENTER }, - { alignX: p5.CENTER, alignY: p5.CENTER }, - { alignX: p5.RIGHT, alignY: p5.CENTER }, - { alignX: p5.LEFT, alignY: p5.BOTTOM }, - { alignX: p5.CENTER, alignY: p5.BOTTOM }, - { alignX: p5.RIGHT, alignY: p5.BOTTOM } - ]; - - p5.createCanvas(150, 100, mode === 'webgl' ? p5.WEBGL : undefined); - if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2); - p5.textSize(20); - - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); - p5.textFont(font); - - let xPos = 20; - let yPos = 20; - const boxWidth = 100; - const boxHeight = 60; - - alignments.forEach((alignment, i) => { - p5.background(255); - p5.push(); - p5.textAlign(alignment.alignX, alignment.alignY); - - p5.noFill(); - p5.stroke(200); - p5.strokeWeight(2); - p5.rect(xPos, yPos, boxWidth, boxHeight); + p5.textFont(font); + alignments.forEach(alignment => { + p5.background(255); + p5.textAlign(alignment.alignX, alignment.alignY); + p5.text('Single Line', p5.width / 2, p5.height / 2); + const bb = p5.textBounds( + 'Single Line', + p5.width / 2, + p5.height / 2 + ); + p5.push(); + p5.noFill(); + p5.stroke('red'); + p5.rect(bb.x, bb.y, bb.w, bb.h); + p5.pop(); + screenshot(); + }); + } + ); - p5.fill(0); - p5.noStroke(); - p5.text('Line 1\nLine 2\nLine 3', xPos, yPos, boxWidth, boxHeight); - const bb = p5.textBounds( - 'Line 1\nLine 2\nLine 3', - xPos, - yPos, - boxWidth, - boxHeight + visualTest( + 'all alignments with single line', + async function (p5, screenshot) { + const alignments = [ + { alignX: p5.LEFT, alignY: p5.TOP }, + { alignX: p5.CENTER, alignY: p5.TOP }, + { alignX: p5.RIGHT, alignY: p5.TOP }, + { alignX: p5.LEFT, alignY: p5.CENTER }, + { alignX: p5.CENTER, alignY: p5.CENTER }, + { alignX: p5.RIGHT, alignY: p5.CENTER }, + { alignX: p5.LEFT, alignY: p5.BOTTOM }, + { alignX: p5.CENTER, alignY: p5.BOTTOM }, + { alignX: p5.RIGHT, alignY: p5.BOTTOM } + ]; + + p5.createCanvas( + 300, + 300, + mode === 'webgl' ? p5.WEBGL : undefined ); - p5.noFill(); - p5.stroke('red'); - p5.rect(bb.x, bb.y, bb.w, bb.h); - p5.pop(); + if (mode === 'webgl') p5.translate(-p5.width / 2, -p5.height / 2); + p5.textSize(45); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' + ); + p5.textFont(font); + alignments.forEach(alignment => { + p5.background(255); + p5.textAlign(alignment.alignX, alignment.alignY); + p5.text('Single Line', p5.width / 2, p5.height / 2); + const bb = p5.textBounds( + 'Single Line', + p5.width / 2, + p5.height / 2 + ); + p5.push(); + p5.noFill(); + p5.stroke('red'); + p5.rect(bb.x, bb.y, bb.w, bb.h); + p5.pop(); + screenshot(); + }); + } + ); - screenshot(); - }); - } - ); + visualTest( + 'all alignments with multi-lines and wrap word', + async function (p5, screenshot) { + const alignments = [ + { alignX: p5.LEFT, alignY: p5.TOP }, + { alignX: p5.CENTER, alignY: p5.TOP }, + { alignX: p5.RIGHT, alignY: p5.TOP }, + { alignX: p5.LEFT, alignY: p5.CENTER }, + { alignX: p5.CENTER, alignY: p5.CENTER }, + { alignX: p5.RIGHT, alignY: p5.CENTER }, + { alignX: p5.LEFT, alignY: p5.BOTTOM }, + { alignX: p5.CENTER, alignY: p5.BOTTOM }, + { alignX: p5.RIGHT, alignY: p5.BOTTOM } + ]; + + p5.createCanvas( + 150, + 100, + mode === 'webgl' ? p5.WEBGL : undefined + ); + if (mode === 'webgl') p5.translate(-p5.width / 2, -p5.height / 2); + p5.textSize(20); + p5.textWrap(p5.WORD); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' + ); + p5.textFont(font); + + let xPos = 20; + let yPos = 20; + const boxWidth = 100; + const boxHeight = 60; + + alignments.forEach((alignment, i) => { + p5.background(255); + p5.push(); + p5.textAlign(alignment.alignX, alignment.alignY); + + p5.noFill(); + p5.strokeWeight(2); + p5.stroke(200); + p5.rect(xPos, yPos, boxWidth, boxHeight); + + p5.fill(0); + p5.noStroke(); + p5.text( + 'A really long text that should wrap automatically as it reaches the end of the box', + xPos, + yPos, + boxWidth, + boxHeight + ); + const bb = p5.textBounds( + 'A really long text that should wrap automatically as it reaches the end of the box', + xPos, + yPos, + boxWidth, + boxHeight + ); + p5.noFill(); + p5.stroke('red'); + p5.rect(bb.x, bb.y, bb.w, bb.h); + p5.pop(); + + screenshot(); + }); + } + ); - visualTest( - 'all alignments with multi-line manual text, no box dimensions', - async function (p5, screenshot) { - const alignments = [ - { alignX: p5.LEFT, alignY: p5.TOP }, - { alignX: p5.CENTER, alignY: p5.TOP }, - { alignX: p5.RIGHT, alignY: p5.TOP }, - { alignX: p5.LEFT, alignY: p5.CENTER }, - { alignX: p5.CENTER, alignY: p5.CENTER }, - { alignX: p5.RIGHT, alignY: p5.CENTER }, - { alignX: p5.LEFT, alignY: p5.BOTTOM }, - { alignX: p5.CENTER, alignY: p5.BOTTOM }, - { alignX: p5.RIGHT, alignY: p5.BOTTOM } - ]; - - p5.createCanvas(150, 100, mode === 'webgl' ? p5.WEBGL : undefined); - if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2); - p5.textSize(20); + visualTest( + 'all alignments with multi-lines and wrap char', + async function (p5, screenshot) { + const alignments = [ + { alignX: p5.LEFT, alignY: p5.TOP }, + { alignX: p5.CENTER, alignY: p5.TOP }, + { alignX: p5.RIGHT, alignY: p5.TOP }, + { alignX: p5.LEFT, alignY: p5.CENTER }, + { alignX: p5.CENTER, alignY: p5.CENTER }, + { alignX: p5.RIGHT, alignY: p5.CENTER }, + { alignX: p5.LEFT, alignY: p5.BOTTOM }, + { alignX: p5.CENTER, alignY: p5.BOTTOM }, + { alignX: p5.RIGHT, alignY: p5.BOTTOM } + ]; + + p5.createCanvas( + 150, + 100, + mode === 'webgl' ? p5.WEBGL : undefined + ); + if (mode === 'webgl') p5.translate(-p5.width / 2, -p5.height / 2); + p5.textSize(19); + p5.textWrap(p5.CHAR); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' + ); + p5.textFont(font); + + let xPos = 20; + let yPos = 20; + const boxWidth = 100; + const boxHeight = 60; + + alignments.forEach((alignment, i) => { + p5.background(255); + p5.push(); + p5.textAlign(alignment.alignX, alignment.alignY); + + p5.noFill(); + p5.strokeWeight(2); + p5.stroke(200); + p5.rect(xPos, yPos, boxWidth, boxHeight); + + p5.fill(0); + p5.noStroke(); + p5.text( + 'A really long text that should wrap automatically as it reaches the end of the box', + xPos, + yPos, + boxWidth, + boxHeight + ); + const bb = p5.textBounds( + 'A really long text that should wrap automatically as it reaches the end of the box', + xPos, + yPos, + boxWidth, + boxHeight + ); + p5.noFill(); + p5.stroke('red'); + p5.rect(bb.x, bb.y, bb.w, bb.h); + p5.pop(); + + screenshot(); + }); + } + ); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); - p5.textFont(font); + visualTest( + 'all alignments with multi-line manual text', + async function (p5, screenshot) { + const alignments = [ + { alignX: p5.LEFT, alignY: p5.TOP }, + { alignX: p5.CENTER, alignY: p5.TOP }, + { alignX: p5.RIGHT, alignY: p5.TOP }, + { alignX: p5.LEFT, alignY: p5.CENTER }, + { alignX: p5.CENTER, alignY: p5.CENTER }, + { alignX: p5.RIGHT, alignY: p5.CENTER }, + { alignX: p5.LEFT, alignY: p5.BOTTOM }, + { alignX: p5.CENTER, alignY: p5.BOTTOM }, + { alignX: p5.RIGHT, alignY: p5.BOTTOM } + ]; + + p5.createCanvas( + 150, + 100, + mode === 'webgl' ? p5.WEBGL : undefined + ); + if (mode === 'webgl') p5.translate(-p5.width / 2, -p5.height / 2); + p5.textSize(20); - let xPos = 20; - let yPos = 20; + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' + ); + p5.textFont(font); + + let xPos = 20; + let yPos = 20; + const boxWidth = 100; + const boxHeight = 60; + + alignments.forEach((alignment, i) => { + p5.background(255); + p5.push(); + p5.textAlign(alignment.alignX, alignment.alignY); + + p5.noFill(); + p5.stroke(200); + p5.strokeWeight(2); + p5.rect(xPos, yPos, boxWidth, boxHeight); + + p5.fill(0); + p5.noStroke(); + p5.text( + 'Line 1\nLine 2\nLine 3', + xPos, + yPos, + boxWidth, + boxHeight + ); + const bb = p5.textBounds( + 'Line 1\nLine 2\nLine 3', + xPos, + yPos, + boxWidth, + boxHeight + ); + p5.noFill(); + p5.stroke('red'); + p5.rect(bb.x, bb.y, bb.w, bb.h); + p5.pop(); + + screenshot(); + }); + } + ); - alignments.forEach((alignment, i) => { - p5.background(255); - p5.push(); - p5.textAlign(alignment.alignX, alignment.alignY); + visualTest( + 'all alignments with multi-line manual text, no box dimensions', + async function (p5, screenshot) { + const alignments = [ + { alignX: p5.LEFT, alignY: p5.TOP }, + { alignX: p5.CENTER, alignY: p5.TOP }, + { alignX: p5.RIGHT, alignY: p5.TOP }, + { alignX: p5.LEFT, alignY: p5.CENTER }, + { alignX: p5.CENTER, alignY: p5.CENTER }, + { alignX: p5.RIGHT, alignY: p5.CENTER }, + { alignX: p5.LEFT, alignY: p5.BOTTOM }, + { alignX: p5.CENTER, alignY: p5.BOTTOM }, + { alignX: p5.RIGHT, alignY: p5.BOTTOM } + ]; + + p5.createCanvas( + 150, + 100, + mode === 'webgl' ? p5.WEBGL : undefined + ); + if (mode === 'webgl') p5.translate(-p5.width / 2, -p5.height / 2); + p5.textSize(20); - p5.fill(0); - p5.noStroke(); - p5.text('Line 1\nLine 2\nLine 3', xPos, yPos); - const bb = p5.textBounds( - 'Line 1\nLine 2\nLine 3', - xPos, - yPos + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' ); - p5.noFill(); - p5.stroke('red'); - p5.rect(bb.x, bb.y, bb.w, bb.h); - p5.pop(); + p5.textFont(font); + + let xPos = 20; + let yPos = 20; + + alignments.forEach((alignment, i) => { + p5.background(255); + p5.push(); + p5.textAlign(alignment.alignX, alignment.alignY); + + p5.fill(0); + p5.noStroke(); + p5.text('Line 1\nLine 2\nLine 3', xPos, yPos); + const bb = p5.textBounds('Line 1\nLine 2\nLine 3', xPos, yPos); + p5.noFill(); + p5.stroke('red'); + p5.rect(bb.x, bb.y, bb.w, bb.h); + p5.pop(); + + screenshot(); + }); + } + ); + }); + } + }); - screenshot(); - }); - } + visualSuite('textStyle', function () { + visualTest('all text styles', async function (p5, screenshot) { + p5.createCanvas(150, 150); + const font = await p5.loadFont( + 'https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&display=swap' ); + p5.textSize(20); + p5.textFont(font); + p5.textAlign(p5.LEFT, p5.TOP); + + p5.text('Regular Text', 0, 0); + p5.textStyle(p5.BOLD); + p5.text('Bold Text', 0, 30); + p5.textStyle(p5.ITALIC); + p5.text('Italic Text', 0, 60); + p5.textStyle(p5.BOLDITALIC); + p5.text('Bold Italic Text', 0, 90); + screenshot(); }); - } - }); - - visualSuite('textStyle', function () { - visualTest('all text styles', async function (p5, screenshot) { - p5.createCanvas(150, 150); - const font = await p5.loadFont( - 'https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&display=swap' - ); - p5.textSize(20); - p5.textFont(font); - p5.textAlign(p5.LEFT, p5.TOP); - - p5.text('Regular Text', 0, 0); - p5.textStyle(p5.BOLD); - p5.text('Bold Text', 0, 30); - p5.textStyle(p5.ITALIC); - p5.text('Italic Text', 0, 60); - p5.textStyle(p5.BOLDITALIC); - p5.text('Bold Italic Text', 0, 90); - screenshot(); }); - }); - visualSuite('textSize', function () { - const units = ['px']; - const sizes = [12, 16, 20, 24, 30]; + visualSuite('textSize', function () { + const units = ['px']; + const sizes = [12, 16, 20, 24, 30]; - visualTest('text sizes comparison', function (p5, screenshot) { - p5.createCanvas(300, 200); - let yOffset = 0; + visualTest('text sizes comparison', function (p5, screenshot) { + p5.createCanvas(300, 200); + let yOffset = 0; - units.forEach(unit => { - sizes.forEach(size => { - p5.textSize(size); - p5.textAlign(p5.LEFT, p5.TOP); - p5.text(`Size: ${size}${unit}`, 0, yOffset); - yOffset += 30; + units.forEach(unit => { + sizes.forEach(size => { + p5.textSize(size); + p5.textAlign(p5.LEFT, p5.TOP); + p5.text(`Size: ${size}${unit}`, 0, yOffset); + yOffset += 30; + }); }); + screenshot(); }); - screenshot(); - }); - }); - - visualSuite('textLeading', function () { - visualTest('text leading with different values', function (p5, screenshot) { - p5.createCanvas(300, 200); - const leadingValues = [10, 20, 30]; - let yOffset = 0; - - p5.textSize(20); - p5.textAlign(p5.LEFT, p5.TOP); - - leadingValues.forEach(leading => { - p5.textLeading(leading); - p5.text(`Leading: ${leading}`, 0, yOffset); - p5.text('This is a line of text.', 0, yOffset + 30); - p5.text('This is another line of text.', 0, yOffset + 30 + leading); - yOffset += 30 + leading; - }); - screenshot(); }); - }); - - visualSuite('textWidth', function () { - visualTest('verify width of a string', function (p5, screenshot) { - p5.createCanvas(100, 100); - p5.textSize(20); - const text = 'Width Test'; - const width = p5.textWidth(text); - p5.text(text, 0, 30); - p5.noFill(); - p5.stroke('red'); - p5.rect(0, 30 - 20, width, 20); - screenshot(); - }); - }); - visualSuite('textToPoints', function () { - visualTest('Fonts can be converted to points', async function (p5, screenshot) { - p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); - p5.background(255); - p5.strokeWeight(2); - p5.textSize(50); - const pts = font.textToPoints('p5*js', 0, 50); - p5.beginShape(p5.POINTS); - for (const { x, y } of pts) p5.vertex(x, y); - p5.endShape(); - screenshot(); - }); + visualSuite('textLeading', function () { + visualTest( + 'text leading with different values', + function (p5, screenshot) { + p5.createCanvas(300, 200); + const leadingValues = [10, 20, 30]; + let yOffset = 0; + + p5.textSize(20); + p5.textAlign(p5.LEFT, p5.TOP); - visualTest('Sampling density can be changed', async function (p5, screenshot) { - p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' + leadingValues.forEach(leading => { + p5.textLeading(leading); + p5.text(`Leading: ${leading}`, 0, yOffset); + p5.text('This is a line of text.', 0, yOffset + 30); + p5.text('This is another line of text.', 0, yOffset + 30 + leading); + yOffset += 30 + leading; + }); + screenshot(); + } ); - p5.background(255); - p5.strokeWeight(2); - p5.textSize(50); - const pts = font.textToPoints('p5*js', 0, 50, { sampleFactor: 0.5 }); - p5.beginShape(p5.POINTS); - for (const { x, y } of pts) p5.vertex(x, y); - p5.endShape(); - screenshot(); }); - for (const mode of ['RADIANS', 'DEGREES']) { - visualTest(`Fonts point angles work in ${mode} mode`, async function (p5, screenshot) { + visualSuite('textWidth', function () { + visualTest('verify width of a string', function (p5, screenshot) { p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); - p5.background(255); - p5.strokeWeight(2); - p5.textSize(50); - p5.angleMode(p5[mode]); - const pts = font.textToPoints('p5*js', 0, 50, { sampleFactor: 0.25 }); - p5.beginShape(p5.LINES); - for (const { x, y, angle } of pts) { - p5.vertex( - x - 5 * p5.cos(angle), - y - 5 * p5.sin(angle) + p5.textSize(20); + const text = 'Width Test'; + const width = p5.textWidth(text); + p5.text(text, 0, 30); + p5.noFill(); + p5.stroke('red'); + p5.rect(0, 30 - 20, width, 20); + screenshot(); + }); + }); + + visualSuite('textToPoints', function () { + visualTest( + 'Fonts can be converted to points', + async function (p5, screenshot) { + p5.createCanvas(100, 100); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' ); - p5.vertex( - x + 5 * p5.cos(angle), - y + 5 * p5.sin(angle) + p5.background(255); + p5.strokeWeight(2); + p5.textSize(50); + const pts = font.textToPoints('p5*js', 0, 50); + p5.beginShape(p5.POINTS); + for (const { x, y } of pts) p5.vertex(x, y); + p5.endShape(); + screenshot(); + } + ); + + visualTest( + 'Sampling density can be changed', + async function (p5, screenshot) { + p5.createCanvas(100, 100); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' ); + p5.background(255); + p5.strokeWeight(2); + p5.textSize(50); + const pts = font.textToPoints('p5*js', 0, 50, { sampleFactor: 0.5 }); + p5.beginShape(p5.POINTS); + for (const { x, y } of pts) p5.vertex(x, y); + p5.endShape(); + screenshot(); } - p5.endShape(); - screenshot(); - }); - } - }); - - visualSuite('textToContours', function () { - visualTest('Fonts can be converted to points grouped by contour', async function (p5, screenshot) { - p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' ); - p5.background(200); - p5.strokeWeight(2); - p5.textSize(50); - const contours = font.textToContours('p5*js', 0, 50, { sampleFactor: 0.5 }); - p5.beginShape(); - for (const pts of contours) { - p5.beginContour(); - for (const { x, y } of pts) p5.vertex(x, y); - p5.endContour(p5.CLOSE); + + for (const mode of ['RADIANS', 'DEGREES']) { + visualTest( + `Fonts point angles work in ${mode} mode`, + async function (p5, screenshot) { + p5.createCanvas(100, 100); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' + ); + p5.background(255); + p5.strokeWeight(2); + p5.textSize(50); + p5.angleMode(p5[mode]); + const pts = font.textToPoints('p5*js', 0, 50, { + sampleFactor: 0.25 + }); + p5.beginShape(p5.LINES); + for (const { x, y, angle } of pts) { + p5.vertex(x - 5 * p5.cos(angle), y - 5 * p5.sin(angle)); + p5.vertex(x + 5 * p5.cos(angle), y + 5 * p5.sin(angle)); + } + p5.endShape(); + screenshot(); + } + ); } - p5.endShape(); - screenshot(); }); - }); - visualSuite('textToPaths', function () { - visualTest('Fonts can be converted to drawing context commands', async function (p5, screenshot) { - p5.createCanvas(100, 100); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' + visualSuite('textToContours', function () { + visualTest( + 'Fonts can be converted to points grouped by contour', + async function (p5, screenshot) { + p5.createCanvas(100, 100); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' + ); + p5.background(200); + p5.strokeWeight(2); + p5.textSize(50); + const contours = font.textToContours('p5*js', 0, 50, { + sampleFactor: 0.5 + }); + p5.beginShape(); + for (const pts of contours) { + p5.beginContour(); + for (const { x, y } of pts) p5.vertex(x, y); + p5.endContour(p5.CLOSE); + } + p5.endShape(); + screenshot(); + } ); - p5.background(200); - p5.strokeWeight(2); - p5.textSize(50); - const cmds = font.textToPaths('p5*js', 0, 50); - p5.drawingContext.beginPath(); - for (const [type, ...args] of cmds) { - if (type === 'M') { - p5.drawingContext.moveTo(...args); - } else if (type === 'L') { - p5.drawingContext.lineTo(...args); - } else if (type === 'C') { - p5.drawingContext.bezierCurveTo(...args); - } else if (type === 'Q') { - p5.drawingContext.quadraticCurveTo(...args); - } else if (type === 'Z') { - p5.drawingContext.closePath(); + }); + + visualSuite('textToPaths', function () { + visualTest( + 'Fonts can be converted to drawing context commands', + async function (p5, screenshot) { + p5.createCanvas(100, 100); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' + ); + p5.background(200); + p5.strokeWeight(2); + p5.textSize(50); + const cmds = font.textToPaths('p5*js', 0, 50); + p5.drawingContext.beginPath(); + for (const [type, ...args] of cmds) { + if (type === 'M') { + p5.drawingContext.moveTo(...args); + } else if (type === 'L') { + p5.drawingContext.lineTo(...args); + } else if (type === 'C') { + p5.drawingContext.bezierCurveTo(...args); + } else if (type === 'Q') { + p5.drawingContext.quadraticCurveTo(...args); + } else if (type === 'Z') { + p5.drawingContext.closePath(); + } + } + p5.drawingContext.fill(); + p5.drawingContext.stroke(); + screenshot(); } - } - p5.drawingContext.fill(); - p5.drawingContext.stroke(); - screenshot(); + ); }); - }); -}, { shiftThreshold: 3 }); + }, + { shiftThreshold: 3 } +); diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 8edf5523d0..d47888b21b 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -1,80 +1,88 @@ import { vi, afterEach } from 'vitest'; import { visualSuite, visualTest } from '../visualTest'; -visualSuite('WebGL', function() { - visualSuite('Camera', function() { - visualTest('2D objects maintain correct size', function(p5, screenshot) { +visualSuite('WebGL', function () { + visualSuite('Camera', function () { + visualTest('2D objects maintain correct size', function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); p5.noStroke(); p5.fill('red'); p5.rectMode(p5.CENTER); - p5.rect(0, 0, p5.width/2, p5.height/2); + p5.rect(0, 0, p5.width / 2, p5.height / 2); screenshot(); }); - visualTest('Custom camera before and after resize', function(p5, screenshot) { - p5.createCanvas(25, 50, p5.WEBGL); - const cam = p5.createCamera(); - p5.setCamera(cam); - cam.setPosition(-10, -10, 800); - p5.strokeWeight(4); - p5.box(20); - screenshot(); + visualTest( + 'Custom camera before and after resize', + function (p5, screenshot) { + p5.createCanvas(25, 50, p5.WEBGL); + const cam = p5.createCamera(); + p5.setCamera(cam); + cam.setPosition(-10, -10, 800); + p5.strokeWeight(4); + p5.box(20); + screenshot(); - p5.resizeCanvas(50, 25); - p5.box(20); - screenshot(); - }); + p5.resizeCanvas(50, 25); + p5.box(20); + screenshot(); + } + ); - visualTest('Camera settings on framebuffers reset after push/pop', function(p5, screenshot) { - p5.createCanvas(100, 100, p5.WEBGL); - p5.setAttributes({ antialias: true }); - const fbo = p5.createFramebuffer(); + visualTest( + 'Camera settings on framebuffers reset after push/pop', + function (p5, screenshot) { + p5.createCanvas(100, 100, p5.WEBGL); + p5.setAttributes({ antialias: true }); + const fbo = p5.createFramebuffer(); - p5.background(220); - p5.imageMode(p5.CENTER); + p5.background(220); + p5.imageMode(p5.CENTER); - fbo.begin(); - p5.push(); - p5.ortho(); - p5.translate(0, -25); - for (let i = -1; i <= 1; i++) { + fbo.begin(); p5.push(); - p5.translate(i * 35, 0); - p5.box(25, 25, 150); + p5.ortho(); + p5.translate(0, -25); + for (let i = -1; i <= 1; i++) { + p5.push(); + p5.translate(i * 35, 0); + p5.box(25, 25, 150); + p5.pop(); + } p5.pop(); - } - p5.pop(); - - p5.push(); - p5.translate(0, 25); - for (let i = -1; i <= 1; i++) { p5.push(); - p5.translate(i * 35, 0); - p5.box(25, 25, 150); + p5.translate(0, 25); + for (let i = -1; i <= 1; i++) { + p5.push(); + p5.translate(i * 35, 0); + p5.box(25, 25, 150); + p5.pop(); + } p5.pop(); - } - p5.pop(); - fbo.end(); - p5.image(fbo, 0, 0); - screenshot(); - }); + fbo.end(); + p5.image(fbo, 0, 0); + screenshot(); + } + ); - visualTest('Works after perspective() with no args', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background(200); - p5.perspective(); - p5.noStroke(); - p5.lights(); - p5.sphere(20); - screenshot(); - }); + visualTest( + 'Works after perspective() with no args', + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(200); + p5.perspective(); + p5.noStroke(); + p5.lights(); + p5.sphere(20); + screenshot(); + } + ); }); - visualSuite('filter', function() { - visualTest('On the main canvas', function(p5, screenshot) { + visualSuite('filter', function () { + visualTest('On the main canvas', function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); p5.noStroke(); p5.fill('red'); @@ -83,7 +91,7 @@ visualSuite('WebGL', function() { screenshot(); }); - visualTest('On a framebuffer', function(p5, screenshot) { + visualTest('On a framebuffer', function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); const fbo = p5.createFramebuffer({ antialias: true }); fbo.begin(); @@ -97,23 +105,30 @@ visualSuite('WebGL', function() { screenshot(); }); - visualTest('On a framebuffer of a different size from the canvas', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - const fbo = p5.createFramebuffer({ antialias: true, width: 25, height: 100 }); - fbo.begin(); - p5.background('blue'); - p5.fill('red'); - p5.circle(0, 0, 20); - p5.filter(p5.BLUR, 3); - fbo.end(); - p5.imageMode(p5.CENTER); - p5.image(fbo, 0, 0); - screenshot(); - }); + visualTest( + 'On a framebuffer of a different size from the canvas', + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + const fbo = p5.createFramebuffer({ + antialias: true, + width: 25, + height: 100 + }); + fbo.begin(); + p5.background('blue'); + p5.fill('red'); + p5.circle(0, 0, 20); + p5.filter(p5.BLUR, 3); + fbo.end(); + p5.imageMode(p5.CENTER); + p5.image(fbo, 0, 0); + screenshot(); + } + ); visualTest( 'On a framebuffer sized differently from the main canvas', - function(p5, screenshot) { + function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); const fbo = p5.createFramebuffer({ width: 26, @@ -133,10 +148,10 @@ visualSuite('WebGL', function() { ); for (const mode of ['webgl', '2d']) { - visualSuite(`In ${mode} mode`, function() { - const setupSketch = (p5) => { + visualSuite(`In ${mode} mode`, function () { + const setupSketch = p5 => { p5.createCanvas(50, 50, mode === 'webgl' ? p5.WEBGL : p5.P2D); - if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2); + if (mode === 'webgl') p5.translate(-p5.width / 2, -p5.height / 2); p5.clear(); p5.noStroke(); p5.fill('red'); @@ -161,52 +176,58 @@ visualSuite('WebGL', function() { } }; - visualTest('It can apply GRAY', function(p5, screenshot) { + visualTest('It can apply GRAY', function (p5, screenshot) { setupSketch(p5); p5.filter(p5.GRAY); screenshot(); }); - visualTest('It can apply INVERT', function(p5, screenshot) { + visualTest('It can apply INVERT', function (p5, screenshot) { setupSketch(p5); p5.filter(p5.INVERT); screenshot(); }); - visualTest('It can apply THRESHOLD', function(p5, screenshot) { + visualTest('It can apply THRESHOLD', function (p5, screenshot) { setupSketch(p5); p5.filter(p5.THRESHOLD); screenshot(); }); - visualTest('It can apply THRESHOLD with a value', function(p5, screenshot) { - setupSketch(p5); - p5.filter(p5.THRESHOLD, 0.8); - screenshot(); - }); - visualTest('It can apply POSTERIZE', function(p5, screenshot) { + visualTest( + 'It can apply THRESHOLD with a value', + function (p5, screenshot) { + setupSketch(p5); + p5.filter(p5.THRESHOLD, 0.8); + screenshot(); + } + ); + visualTest('It can apply POSTERIZE', function (p5, screenshot) { setupSketch(p5); p5.filter(p5.THRESHOLD); screenshot(); }); - visualTest('It can apply POSTERIZE with a value', function(p5, screenshot) { - setupSketch(p5); - p5.filter(p5.THRESHOLD, 2); - screenshot(); - }); - visualTest('It can apply BLUR', function(p5, screenshot) { + visualTest( + 'It can apply POSTERIZE with a value', + function (p5, screenshot) { + setupSketch(p5); + p5.filter(p5.THRESHOLD, 2); + screenshot(); + } + ); + visualTest('It can apply BLUR', function (p5, screenshot) { setupSketch(p5); p5.filter(p5.BLUR, 5); screenshot(); }); - visualTest('It can apply BLUR with a value', function(p5, screenshot) { + visualTest('It can apply BLUR with a value', function (p5, screenshot) { setupSketch(p5); p5.filter(p5.BLUR, 10); screenshot(); }); - visualTest('It can apply ERODE (4x)', function(p5, screenshot) { + visualTest('It can apply ERODE (4x)', function (p5, screenshot) { setupSketch(p5); for (let i = 0; i < 4; i++) p5.filter(p5.ERODE); screenshot(); }); - visualTest('It can apply DILATE (4x)', function(p5, screenshot) { + visualTest('It can apply DILATE (4x)', function (p5, screenshot) { setupSketch(p5); for (let i = 0; i < 4; i++) p5.filter(p5.DILATE); screenshot(); @@ -215,8 +236,8 @@ visualSuite('WebGL', function() { } for (const mode of ['webgl', '2d']) { - visualSuite(`In ${mode} mode`, function() { - visualTest('It can use filter shader hooks', function(p5, screenshot) { + visualSuite(`In ${mode} mode`, function () { + visualTest('It can use filter shader hooks', function (p5, screenshot) { p5.createCanvas(50, 50, mode === 'webgl' ? p5.WEBGL : p5.P2D); const s = p5.baseFilterShader().modify({ @@ -227,7 +248,7 @@ visualSuite('WebGL', function() { }` }); - if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2); + if (mode === 'webgl') p5.translate(-p5.width / 2, -p5.height / 2); p5.background(255); p5.fill('red'); p5.noStroke(); @@ -240,23 +261,26 @@ visualSuite('WebGL', function() { } for (const mode of ['webgl', '2d']) { - visualSuite(`In ${mode} mode`, function() { - visualTest('It can combine multiple filter passes', function(p5, screenshot) { - p5.createCanvas(50, 50, mode === 'webgl' ? p5.WEBGL : p5.P2D); - if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2); - p5.background(255); - p5.fill(0); - p5.noStroke(); - p5.circle(15, 15, 20); - p5.circle(30, 30, 20); - p5.filter(p5.BLUR, 5); - p5.filter(p5.THRESHOLD); - screenshot(); - }); + visualSuite(`In ${mode} mode`, function () { + visualTest( + 'It can combine multiple filter passes', + function (p5, screenshot) { + p5.createCanvas(50, 50, mode === 'webgl' ? p5.WEBGL : p5.P2D); + if (mode === 'webgl') p5.translate(-p5.width / 2, -p5.height / 2); + p5.background(255); + p5.fill(0); + p5.noStroke(); + p5.circle(15, 15, 20); + p5.circle(30, 30, 20); + p5.filter(p5.BLUR, 5); + p5.filter(p5.THRESHOLD); + screenshot(); + } + ); }); } - visualTest('On a createGraphics WEBGL buffer', function(p5, screenshot) { + visualTest('On a createGraphics WEBGL buffer', function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); const g = p5.createGraphics(50, 50, p5.WEBGL); @@ -274,97 +298,122 @@ visualSuite('WebGL', function() { }); for (const mode of ['webgl', '2d']) { - visualTest(`Transparent background colors are correct in ${mode} mode`, function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - const g = p5.createGraphics(50, 50, mode === 'webgl' ? p5.WEBGL : p5.P2D); - if (mode === 'webgl') g.translate(-p5.width/2, -p5.height/2); - g.noStroke(); - g.fill(255, 0, 0, 100); - g.rect(10, 10, 30, 30); - g.filter(p5.BLUR, 4); - p5.imageMode(p5.CENTER); - p5.image(g, 0, 0); - screenshot(); - }); + visualTest( + `Transparent background colors are correct in ${mode} mode`, + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + const g = p5.createGraphics( + 50, + 50, + mode === 'webgl' ? p5.WEBGL : p5.P2D + ); + if (mode === 'webgl') g.translate(-p5.width / 2, -p5.height / 2); + g.noStroke(); + g.fill(255, 0, 0, 100); + g.rect(10, 10, 30, 30); + g.filter(p5.BLUR, 4); + p5.imageMode(p5.CENTER); + p5.image(g, 0, 0); + screenshot(); + } + ); - visualTest(`Multiple filter passes work correctly on a p5.Graphics in ${mode} mode`, function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - const g = p5.createGraphics(50, 50, mode === 'webgl' ? p5.WEBGL : p5.P2D); - if (mode === 'webgl') g.translate(-g.width/2, -g.height/2); - g.background(255); - g.noStroke(); - g.fill(0); - g.rect(10, 10, 6, 6); - g.filter(p5.BLUR, 2); - g.rect(30, 30, 6, 6); - g.filter(p5.BLUR, 2); - p5.imageMode(p5.CENTER); - p5.image(g, 0, 0); - screenshot(); - }); + visualTest( + `Multiple filter passes work correctly on a p5.Graphics in ${mode} mode`, + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + const g = p5.createGraphics( + 50, + 50, + mode === 'webgl' ? p5.WEBGL : p5.P2D + ); + if (mode === 'webgl') g.translate(-g.width / 2, -g.height / 2); + g.background(255); + g.noStroke(); + g.fill(0); + g.rect(10, 10, 6, 6); + g.filter(p5.BLUR, 2); + g.rect(30, 30, 6, 6); + g.filter(p5.BLUR, 2); + p5.imageMode(p5.CENTER); + p5.image(g, 0, 0); + screenshot(); + } + ); } }); - visualSuite('Lights', function() { - visualTest('Fill color and default ambient material', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - p5.noStroke(); - p5.lights(); - p5.fill('red'); - p5.sphere(20); - screenshot(); - }); + visualSuite('Lights', function () { + visualTest( + 'Fill color and default ambient material', + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.noStroke(); + p5.lights(); + p5.fill('red'); + p5.sphere(20); + screenshot(); + } + ); }); - visualSuite('3DModel', function() { - visualTest('OBJ model with MTL file displays diffuse colors correctly', function(p5, screenshot) { - return new Promise(resolve => { - p5.createCanvas(50, 50, p5.WEBGL); - p5.loadModel('test/unit/assets/octa-color.obj', model => { - p5.background(255); - p5.rotateX(10 * 0.01); - p5.rotateY(10 * 0.01); - model.normalize(); - p5.model(model); - screenshot(); - resolve(); + visualSuite('3DModel', function () { + visualTest( + 'OBJ model with MTL file displays diffuse colors correctly', + function (p5, screenshot) { + return new Promise(resolve => { + p5.createCanvas(50, 50, p5.WEBGL); + p5.loadModel('test/unit/assets/octa-color.obj', model => { + p5.background(255); + p5.rotateX(10 * 0.01); + p5.rotateY(10 * 0.01); + model.normalize(); + p5.model(model); + screenshot(); + resolve(); + }); }); - }); - }); - visualTest('Object with no colors takes on fill color', function(p5, screenshot) { - return new Promise(resolve => { - p5.createCanvas(50, 50, p5.WEBGL); - p5.loadModel('test/unit/assets/cube.obj', model => { - p5.background(255); - p5.fill('blue'); // Setting a fill color - p5.rotateX(p5.frameCount * 0.01); - p5.rotateY(p5.frameCount * 0.01); - model.normalize(); - p5.model(model); - screenshot(); - resolve(); + } + ); + visualTest( + 'Object with no colors takes on fill color', + function (p5, screenshot) { + return new Promise(resolve => { + p5.createCanvas(50, 50, p5.WEBGL); + p5.loadModel('test/unit/assets/cube.obj', model => { + p5.background(255); + p5.fill('blue'); // Setting a fill color + p5.rotateX(p5.frameCount * 0.01); + p5.rotateY(p5.frameCount * 0.01); + model.normalize(); + p5.model(model); + screenshot(); + resolve(); + }); }); - }); - }); + } + ); visualTest( 'Object with different texture coordinates per use of vertex keeps the coordinates intact', - async function(p5, screenshot) { + async function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); const tex = await p5.loadImage('test/unit/assets/cat.jpg'); - const cube = await new Promise(resolve => p5.loadModel('test/unit/assets/cube-textures.obj', resolve)); + const cube = await new Promise(resolve => + p5.loadModel('test/unit/assets/cube-textures.obj', resolve) + ); cube.normalize(); p5.background(255); p5.texture(tex); p5.rotateX(p5.PI / 4); p5.rotateY(p5.PI / 4); - p5.scale(80/400); + p5.scale(80 / 400); p5.model(cube); screenshot(); } ); }); - visualSuite('vertexProperty', function(){ + visualSuite('vertexProperty', function () { const vertSrc = `#version 300 es precision mediump float; uniform mat4 uProjectionMatrix; @@ -383,74 +432,71 @@ visualSuite('WebGL', function() { void main(){ outColor = vec4(vCol, 1.0); }`; - visualTest( - 'on PATH shape mode', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background('white'); - const myShader = p5.createShader(vertSrc, fragSrc); - p5.shader(myShader); - p5.beginShape(p5.PATH); - p5.noStroke(); - for (let i = 0; i < 20; i++){ - let x = 20 * p5.sin(i/20*p5.TWO_PI); - let y = 20 * p5.cos(i/20*p5.TWO_PI); - p5.vertexProperty('aCol', [x/20, -y/20, 0]); - p5.vertex(x, y); - } - p5.endShape(); - screenshot(); + visualTest('on PATH shape mode', function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background('white'); + const myShader = p5.createShader(vertSrc, fragSrc); + p5.shader(myShader); + p5.beginShape(p5.PATH); + p5.noStroke(); + for (let i = 0; i < 20; i++) { + let x = 20 * p5.sin((i / 20) * p5.TWO_PI); + let y = 20 * p5.cos((i / 20) * p5.TWO_PI); + p5.vertexProperty('aCol', [x / 20, -y / 20, 0]); + p5.vertex(x, y); } - ); - visualTest( - 'on QUADS shape mode', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background('white'); - const myShader = p5.createShader(vertSrc, fragSrc); - p5.shader(myShader); - p5.beginShape(p5.QUADS); - p5.noStroke(); - p5.translate(-25,-25); - for (let i = 0; i < 5; i++){ - for (let j = 0; j < 5; j++){ - let x1 = i * 10; - let x2 = x1 + 10; - let y1 = j * 10; - let y2 = y1 + 10; - p5.vertexProperty('aCol', [1, 0, 0]); - p5.vertex(x1, y1); - p5.vertexProperty('aCol', [0, 0, 1]); - p5.vertex(x2, y1); - p5.vertexProperty('aCol', [0, 1, 1]); - p5.vertex(x2, y2); - p5.vertexProperty('aCol', [1, 1, 1]); - p5.vertex(x1, y2); - } + p5.endShape(); + screenshot(); + }); + visualTest('on QUADS shape mode', function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background('white'); + const myShader = p5.createShader(vertSrc, fragSrc); + p5.shader(myShader); + p5.beginShape(p5.QUADS); + p5.noStroke(); + p5.translate(-25, -25); + for (let i = 0; i < 5; i++) { + for (let j = 0; j < 5; j++) { + let x1 = i * 10; + let x2 = x1 + 10; + let y1 = j * 10; + let y2 = y1 + 10; + p5.vertexProperty('aCol', [1, 0, 0]); + p5.vertex(x1, y1); + p5.vertexProperty('aCol', [0, 0, 1]); + p5.vertex(x2, y1); + p5.vertexProperty('aCol', [0, 1, 1]); + p5.vertex(x2, y2); + p5.vertexProperty('aCol', [1, 1, 1]); + p5.vertex(x1, y2); } - p5.endShape(); - screenshot(); } - ); + p5.endShape(); + screenshot(); + }); visualTest( - 'on buildGeometry outputs containing 3D primitives', function(p5, screenshot) { + 'on buildGeometry outputs containing 3D primitives', + function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); p5.background('white'); const myShader = p5.createShader(vertSrc, fragSrc); p5.shader(myShader); const shape = p5.buildGeometry(() => { p5.push(); - p5.translate(15,-10,0); + p5.translate(15, -10, 0); p5.sphere(5); p5.pop(); p5.beginShape(p5.TRIANGLES); - p5.vertexProperty('aCol', [1,0,0]); + p5.vertexProperty('aCol', [1, 0, 0]); p5.vertex(-5, 5, 0); - p5.vertexProperty('aCol', [0,1,0]); + p5.vertexProperty('aCol', [0, 1, 0]); p5.vertex(5, 5, 0); - p5.vertexProperty('aCol', [0,0,1]); + p5.vertexProperty('aCol', [0, 0, 1]); p5.vertex(0, -5, 0); p5.endShape(p5.CLOSE); p5.push(); - p5.translate(-15,10,0); + p5.translate(-15, 10, 0); p5.box(10); p5.pop(); }); @@ -460,7 +506,7 @@ visualSuite('WebGL', function() { ); }); - visualSuite('ShaderFunctionality', function() { + visualSuite('ShaderFunctionality', function () { visualTest('FillShader', async (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); const img = await p5.loadImage('test/unit/assets/cat.jpg'); @@ -505,9 +551,9 @@ visualSuite('WebGL', function() { p5.strokeWeight(15); p5.line( -p5.width / 3, - p5.sin(0.2) * p5.height / 4, + (p5.sin(0.2) * p5.height) / 4, p5.width / 3, - p5.sin(1.2) * p5.height / 4 + (p5.sin(1.2) * p5.height) / 4 ); screenshot(); }); @@ -550,7 +596,9 @@ visualSuite('WebGL', function() { visualTest('loadMaterialShader', async (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const materialShader = await p5.loadMaterialShader('/test/unit/assets/testMaterial.js'); + const materialShader = await p5.loadMaterialShader( + '/test/unit/assets/testMaterial.js' + ); p5.noStroke(); p5.shader(materialShader); @@ -570,94 +618,121 @@ visualSuite('WebGL', function() { } else { p5.fill(0, 255, 0); // Green } - p5.rect(-p5.width/2 + i * 10, -p5.height/2, 10, p5.height); + p5.rect(-p5.width / 2 + i * 10, -p5.height / 2, 10, p5.height); } // Apply the filter shader (should swap red and green channels) - const filterShader = await p5.loadFilterShader('/test/unit/assets/testFilter.js'); + const filterShader = await p5.loadFilterShader( + '/test/unit/assets/testFilter.js' + ); p5.filter(filterShader); screenshot(); }); }); - visualSuite('Strokes', function() { - visualTest('Strokes do not cut into fills in ortho mode', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background(220); - p5.stroke(8); - p5.ortho(); - p5.rotateX(p5.PI/4); - p5.rotateY(p5.PI/4); - p5.box(30); - screenshot(); - }); + visualSuite('Strokes', function () { + visualTest( + 'Strokes do not cut into fills in ortho mode', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(220); + p5.stroke(8); + p5.ortho(); + p5.rotateX(p5.PI / 4); + p5.rotateY(p5.PI / 4); + p5.box(30); + screenshot(); + } + ); }); - visualSuite('Opacity', function() { - visualTest('Basic colors have opacity applied correctly', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background(255); - p5.fill(255, 100, 100, 100); - p5.circle(0, 0, 50); - screenshot(); - }); + visualSuite('Opacity', function () { + visualTest( + 'Basic colors have opacity applied correctly', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(255); + p5.fill(255, 100, 100, 100); + p5.circle(0, 0, 50); + screenshot(); + } + ); - visualTest('Colors have opacity applied correctly when lights are used', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background(255); - p5.ambientLight(255); - p5.fill(255, 100, 100, 100); - p5.circle(0, 0, 50); - screenshot(); - }); + visualTest( + 'Colors have opacity applied correctly when lights are used', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(255); + p5.ambientLight(255); + p5.fill(255, 100, 100, 100); + p5.circle(0, 0, 50); + screenshot(); + } + ); - visualTest('Colors in shader hooks have opacity applied correctly', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const myShader = p5.baseMaterialShader().modify({ - 'Inputs getPixelInputs': `(Inputs inputs) { + visualTest( + 'Colors in shader hooks have opacity applied correctly', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const myShader = p5.baseMaterialShader().modify({ + 'Inputs getPixelInputs': `(Inputs inputs) { inputs.color = vec4(1., 0.4, 0.4, 100./255.); return inputs; }` - }); - p5.background(255); - p5.shader(myShader); - p5.circle(0, 0, 50); - screenshot(); - }); + }); + p5.background(255); + p5.shader(myShader); + p5.circle(0, 0, 50); + screenshot(); + } + ); - visualTest('Colors in textures have opacity applied correctly', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const tex = p5.createFramebuffer(); - tex.draw(() => p5.background(255, 100, 100, 100)); - p5.background(255); - p5.texture(tex); - p5.circle(0, 0, 50); - screenshot(); - }); + visualTest( + 'Colors in textures have opacity applied correctly', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const tex = p5.createFramebuffer(); + tex.draw(() => p5.background(255, 100, 100, 100)); + p5.background(255); + p5.texture(tex); + p5.circle(0, 0, 50); + screenshot(); + } + ); - visualTest('Colors in tinted textures have opacity applied correctly', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const tex = p5.createFramebuffer(); - tex.draw(() => p5.background(255, 100, 100, 255)); - p5.background(255); - p5.texture(tex); - p5.tint(255, 100); - p5.circle(0, 0, 50); - screenshot(); - }); + visualTest( + 'Colors in tinted textures have opacity applied correctly', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const tex = p5.createFramebuffer(); + tex.draw(() => p5.background(255, 100, 100, 255)); + p5.background(255); + p5.texture(tex); + p5.tint(255, 100); + p5.circle(0, 0, 50); + screenshot(); + } + ); - visualTest('noTint() before image() does not throw', async (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const img = await p5.loadImage('/test/unit/assets/cat.jpg'); - p5.noTint(); - p5.imageMode(p5.CENTER); - p5.image(img, 0, 0, 50, 50); - screenshot(); - }); + visualTest( + 'noTint() before image() does not throw', + async (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const img = await p5.loadImage('/test/unit/assets/cat.jpg'); + p5.noTint(); + p5.imageMode(p5.CENTER); + p5.image(img, 0, 0, 50, 50); + screenshot(); + } + ); }); visualSuite('Hooks coordinate spaces', () => { - for (const base of ['baseMaterialShader', 'baseColorShader', 'baseNormalShader']) { + for (const base of [ + 'baseMaterialShader', + 'baseColorShader', + 'baseNormalShader' + ]) { visualSuite(base, () => { visualTest('Object space', (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); @@ -673,7 +748,7 @@ visualSuite('WebGL', function() { p5.lights(); p5.fill('red'); p5.noStroke(); - p5.rotateY(p5.PI/2); + p5.rotateY(p5.PI / 2); p5.camera(-800, 0, 0, 0, 0, 0); p5.shader(myShader); p5.sphere(20); @@ -694,7 +769,7 @@ visualSuite('WebGL', function() { p5.lights(); p5.fill('red'); p5.noStroke(); - p5.rotateY(p5.PI/2); + p5.rotateY(p5.PI / 2); p5.camera(-800, 0, 0, 0, 0, 0); p5.shader(myShader); p5.sphere(20); @@ -715,7 +790,7 @@ visualSuite('WebGL', function() { p5.lights(); p5.fill('red'); p5.noStroke(); - p5.rotateY(p5.PI/2); + p5.rotateY(p5.PI / 2); p5.camera(-800, 0, 0, 0, 0, 0); p5.shader(myShader); p5.sphere(20); @@ -752,9 +827,7 @@ visualSuite('WebGL', function() { visualSuite('textToModel', () => { visualTest('Flat', async (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); + const font = await p5.loadFont('test/unit/assets/Inconsolata-Bold.ttf'); p5.textSize(20); const geom = font.textToModel('p5*js', 0, 0, { sampleFactor: 2 @@ -762,18 +835,16 @@ visualSuite('WebGL', function() { geom.normalize(); p5.background(255); p5.normalMaterial(); - p5.rotateX(p5.PI*0.1); - p5.rotateY(p5.PI*0.1); - p5.scale(50/200); + p5.rotateX(p5.PI * 0.1); + p5.rotateY(p5.PI * 0.1); + p5.scale(50 / 200); p5.model(geom); screenshot(); }); visualTest('Extruded', async (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); + const font = await p5.loadFont('test/unit/assets/Inconsolata-Bold.ttf'); p5.textSize(20); const geom = font.textToModel('p5*js', 0, 0, { extrude: 10, @@ -782,9 +853,9 @@ visualSuite('WebGL', function() { geom.normalize(); p5.background(255); p5.normalMaterial(); - p5.rotateX(p5.PI*0.1); - p5.rotateY(p5.PI*0.1); - p5.scale(50/200); + p5.rotateX(p5.PI * 0.1); + p5.rotateY(p5.PI * 0.1); + p5.scale(50 / 200); p5.model(geom); screenshot(); }); @@ -843,14 +914,14 @@ visualSuite('WebGL', function() { const geom = p5.buildGeometry(() => { p5.push(); - p5.translate(-p5.width*0.2, 0); + p5.translate(-p5.width * 0.2, 0); p5.scale(0.15); p5.sphere(); p5.pop(); p5.push(); p5.fill('red'); - p5.translate(p5.width*0.2, 0); + p5.translate(p5.width * 0.2, 0); p5.scale(0.15); p5.sphere(); p5.pop(); @@ -868,53 +939,55 @@ visualSuite('WebGL', function() { vi.restoreAllMocks(); }); - visualTest('glyph resource allocation does not corrupt textures', async (p5, screenshot) => { - p5.createCanvas(100, 100, p5.WEBGL); - vi.spyOn(p5._renderer, 'maxCachedGlyphs').mockReturnValue(6); - - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); + visualTest( + 'glyph resource allocation does not corrupt textures', + async (p5, screenshot) => { + p5.createCanvas(100, 100, p5.WEBGL); + vi.spyOn(p5._renderer, 'maxCachedGlyphs').mockReturnValue(6); + + const font = await p5.loadFont('test/unit/assets/Inconsolata-Bold.ttf'); + + p5.textFont(font); + p5.clear(); + p5.textSize(10); + p5.textAlign(p5.LEFT, p5.TOP); + for (let i = 0; i < 100; i++) { + const x = -p5.width / 2 + (i % 10) * 10; + const y = -p5.height / 2 + p5.floor(i / 10) * 10; + p5.text(String.fromCharCode(33 + i), x, y); + } - p5.textFont(font); - p5.clear(); - p5.textSize(10); - p5.textAlign(p5.LEFT, p5.TOP); - for (let i = 0; i < 100; i++) { - const x = -p5.width/2 + (i % 10) * 10; - const y = -p5.height/2 + p5.floor(i / 10) * 10; - p5.text(String.fromCharCode(33 + i), x, y); + screenshot(); } + ); - screenshot(); - }); - - visualTest('text renders correctly after geometry with many indices', async (p5, screenshot) => { - p5.createCanvas(100, 100, p5.WEBGL); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); + visualTest( + 'text renders correctly after geometry with many indices', + async (p5, screenshot) => { + p5.createCanvas(100, 100, p5.WEBGL); + const font = await p5.loadFont('test/unit/assets/Inconsolata-Bold.ttf'); - p5.background(255); - p5.noStroke(); + p5.background(255); + p5.noStroke(); - p5.textFont(font); - p5.textSize(20); - p5.textAlign(p5.CENTER, p5.CENTER); - p5.text('Test 1', 0, -20); + p5.textFont(font); + p5.textSize(20); + p5.textAlign(p5.CENTER, p5.CENTER); + p5.text('Test 1', 0, -20); - // Draw a sphere which has many more indices than text - p5.fill(200, 200, 255); - p5.sphere(30); + // Draw a sphere which has many more indices than text + p5.fill(200, 200, 255); + p5.sphere(30); - p5.clearDepth(); + p5.clearDepth(); - // Draw text - should bind its own index buffer - p5.fill(0); - p5.text('Test 2', 0, 20); + // Draw text - should bind its own index buffer + p5.fill(0); + p5.text('Test 2', 0, 20); - screenshot(); - }); + screenshot(); + } + ); }); visualSuite('texture()', () => { @@ -939,13 +1012,16 @@ visualSuite('WebGL', function() { visualTest('uniformTexture() works', async (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); const tex = await p5.loadImage('test/unit/assets/cat.jpg'); - const shader = p5.baseMaterialShader().modify(() => { - const texUniform = p5.uniformTexture(() => tex) - p5.getPixelInputs((inputs) => { - inputs.color = p5.getTexture(texUniform, inputs.texCoord); - return inputs; - }); - }, { p5, tex }); + const shader = p5.baseMaterialShader().modify( + () => { + const texUniform = p5.uniformTexture(() => tex); + p5.getPixelInputs(inputs => { + inputs.color = p5.getTexture(texUniform, inputs.texCoord); + return inputs; + }); + }, + { p5, tex } + ); p5.shader(shader); p5.rect(-20, -20, 40, 40); screenshot(); @@ -969,14 +1045,18 @@ visualSuite('WebGL', function() { positionData.pixels[i * 4 + 3] = 1; } positionData.updatePixels(); - const sh = p5.baseMaterialShader().modify(() => { - const data = p5.uniformTexture(() => positionData); - p5.getWorldInputs((inputs) => { - const angle = p5.getTexture(data, [p5.instanceIndex/3, 0]).r * p5.TWO_PI; - inputs.position.xy += [p5.cos(angle) * 10, p5.sin(angle) * 10]; - return inputs; - }); - }, { p5, positionData }); + const sh = p5.baseMaterialShader().modify( + () => { + const data = p5.uniformTexture(() => positionData); + p5.getWorldInputs(inputs => { + const angle = + p5.getTexture(data, [p5.instanceIndex / 3, 0]).r * p5.TWO_PI; + inputs.position.xy += [p5.cos(angle) * 10, p5.sin(angle) * 10]; + return inputs; + }); + }, + { p5, positionData } + ); const instance = p5.buildGeometry(() => p5.sphere(3)); p5.noStroke(); @@ -987,7 +1067,7 @@ visualSuite('WebGL', function() { }); }); - visualSuite("Image Based Lighting", function () { + visualSuite('Image Based Lighting', function () { const shinesses = [50, 150]; for (const shininess of shinesses) { visualTest( @@ -1029,21 +1109,26 @@ visualSuite('WebGL', function() { visualSuite('instanced randering', async () => { visualTest('can draw in a grid with floor()', (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseMaterialShader().modify(() => { - p5.getWorldInputs((inputs) => { - const id = p5.instanceIndex; - const gridSize = 5; - const row = p5.floor(id / gridSize); - const col = id - row * gridSize; - const blockInnerSize = 10; - const x = (col - gridSize / 2.0) * blockInnerSize + blockInnerSize/2; - const y = (gridSize / 2.0 - row) * blockInnerSize - blockInnerSize/2; - inputs.position += [x, y, 0]; - return inputs; - }); - }, { p5 }); + const shader = p5.baseMaterialShader().modify( + () => { + p5.getWorldInputs(inputs => { + const id = p5.instanceIndex; + const gridSize = 5; + const row = p5.floor(id / gridSize); + const col = id - row * gridSize; + const blockInnerSize = 10; + const x = + (col - gridSize / 2.0) * blockInnerSize + blockInnerSize / 2; + const y = + (gridSize / 2.0 - row) * blockInnerSize - blockInnerSize / 2; + inputs.position += [x, y, 0]; + return inputs; + }); + }, + { p5 } + ); p5.shader(shader); - const obj = p5.buildGeometry(() => p5.circle(0, 0, 6)) + const obj = p5.buildGeometry(() => p5.circle(0, 0, 6)); p5.noStroke(); p5.fill(0); p5.shader(shader); @@ -1053,54 +1138,65 @@ visualSuite('WebGL', function() { visualTest('can draw in a grid with int()', (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseMaterialShader().modify(() => { - p5.getWorldInputs((inputs) => { - const id = p5.instanceIndex; - const gridSize = 5; - const row = p5.int(id / gridSize); - const col = id - row * gridSize; - const blockInnerSize = 10; - const x = (col - gridSize / 2.0) * blockInnerSize + blockInnerSize/2; - const y = (gridSize / 2.0 - row) * blockInnerSize - blockInnerSize/2; - inputs.position += [x, y, 0]; - return inputs; - }); - }, { p5 }); + const shader = p5.baseMaterialShader().modify( + () => { + p5.getWorldInputs(inputs => { + const id = p5.instanceIndex; + const gridSize = 5; + const row = p5.int(id / gridSize); + const col = id - row * gridSize; + const blockInnerSize = 10; + const x = + (col - gridSize / 2.0) * blockInnerSize + blockInnerSize / 2; + const y = + (gridSize / 2.0 - row) * blockInnerSize - blockInnerSize / 2; + inputs.position += [x, y, 0]; + return inputs; + }); + }, + { p5 } + ); p5.shader(shader); - const obj = p5.buildGeometry(() => p5.circle(0, 0, 6)) + const obj = p5.buildGeometry(() => p5.circle(0, 0, 6)); p5.noStroke(); p5.fill(0); p5.shader(shader); p5.model(obj, 25); screenshot(); }); - visualTest('instanceID in fragment hook colors instances', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const numInstances = 4; - const shader = p5.baseMaterialShader().modify(() => { - // Vertex hook: position instances in a horizontal row - p5.getWorldInputs((inputs) => { - const id = p5.instanceIndex; - const spacing = 12; - const offset = (id - (numInstances - 1) / 2.0) * spacing; - inputs.position.x += offset; - return inputs; - }); - // Fragment hook: color each instance based on instanceID - p5.getFinalColor((color) => { - const id = p5.instanceIndex; - const t = id / (numInstances - 1.0); - color = [t, t, t, 1]; - return color; - }); - }, { p5, numInstances }); - p5.background(128); - p5.noStroke(); - p5.shader(shader); - const obj = p5.buildGeometry(() => p5.circle(0, 0, 10)); - p5.model(obj, numInstances); - screenshot(); - }); + visualTest( + 'instanceID in fragment hook colors instances', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const numInstances = 4; + const shader = p5.baseMaterialShader().modify( + () => { + // Vertex hook: position instances in a horizontal row + p5.getWorldInputs(inputs => { + const id = p5.instanceIndex; + const spacing = 12; + const offset = (id - (numInstances - 1) / 2.0) * spacing; + inputs.position.x += offset; + return inputs; + }); + // Fragment hook: color each instance based on instanceID + p5.getFinalColor(color => { + const id = p5.instanceIndex; + const t = id / (numInstances - 1.0); + color = [t, t, t, 1]; + return color; + }); + }, + { p5, numInstances } + ); + p5.background(128); + p5.noStroke(); + p5.shader(shader); + const obj = p5.buildGeometry(() => p5.circle(0, 0, 10)); + p5.model(obj, numInstances); + screenshot(); + } + ); }); visualSuite('p5.strands', () => { @@ -1118,14 +1214,17 @@ visualSuite('WebGL', function() { visualTest('random() colors a basic shader', (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseColorShader().modify(() => { - p5.randomSeed(12); - p5.getFinalColor((color) => { - const value = p5.random(0.2, 0.9); - color = [value, value, value, 1]; - return color; - }); - }, { p5 }); + const shader = p5.baseColorShader().modify( + () => { + p5.randomSeed(12); + p5.getFinalColor(color => { + const value = p5.random(0.2, 0.9); + color = [value, value, value, 1]; + return color; + }); + }, + { p5 } + ); p5.background(0); p5.noStroke(); p5.shader(shader); @@ -1134,52 +1233,64 @@ visualSuite('WebGL', function() { }); visualTest('randomGaussian() colors a basic shader', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseColorShader().modify(() => { - p5.randomSeed(12); - p5.getFinalColor((color) => { - const value = p5.randomGaussian(0.5, 0.1); - color = [value, value, value, 1]; - return color; + p5.createCanvas(50, 50, p5.WEBGL); + const shader = p5.baseColorShader().modify( + () => { + p5.randomSeed(12); + p5.getFinalColor(color => { + const value = p5.randomGaussian(0.5, 0.1); + color = [value, value, value, 1]; + return color; + }); + }, + { p5 } + ); + p5.background(0); + p5.noStroke(); + p5.shader(shader); + p5.plane(50, 50); + screenshot(); }); - }, { p5 }); - p5.background(0); - p5.noStroke(); - p5.shader(shader); - p5.plane(50, 50); - screenshot(); -}); -visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseMaterialShader().modify(() => { - p5.randomSeed(7); - p5.getPixelInputs(inputs => { - let sum = p5.float(0.0); - for (let i = 0; i < 20; i++) { - sum = sum + p5.randomGaussian(0.5, 0.2); + visualTest( + 'randomGaussian() in a fragment loop averages to the mean', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const shader = p5.baseMaterialShader().modify( + () => { + p5.randomSeed(7); + p5.getPixelInputs(inputs => { + let sum = p5.float(0.0); + for (let i = 0; i < 20; i++) { + sum = sum + p5.randomGaussian(0.5, 0.2); + } + const avg = sum / 20; + inputs.color = [avg, avg, avg, 1.0]; + return inputs; + }); + }, + { p5 } + ); + p5.background(0); + p5.noStroke(); + p5.shader(shader); + p5.plane(50, 50); + screenshot(); } - const avg = sum / 20; - inputs.color = [avg, avg, avg, 1.0]; - return inputs; - }); - }, { p5 }); - p5.background(0); - p5.noStroke(); - p5.shader(shader); - p5.plane(50, 50); - screenshot(); -}); + ); visualTest('uses width/height in getFinalColor', (p5, screenshot) => { p5.createCanvas(60, 60, p5.WEBGL); p5.pixelDensity(1); - const firstShader = p5.baseColorShader().modify(() => { - p5.getFinalColor((color) => { - color = [p5.width / 60, p5.height / 60, 0, 1]; - return color; - }); - }, { p5 }); + const firstShader = p5.baseColorShader().modify( + () => { + p5.getFinalColor(color => { + color = [p5.width / 60, p5.height / 60, 0, 1]; + return color; + }); + }, + { p5 } + ); p5.background(0); p5.shader(firstShader); p5.noStroke(); @@ -1190,16 +1301,15 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre visualTest('lerp maps to mix in strands context', (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); // lerp should behave identically to mix inside strands - const shader = p5.baseColorShader().modify(() => { - p5.getFinalColor((color) => { - color = p5.lerp( - [1, 0, 0, 1], - [0, 0, 1, 1], - 0.5 - ); - return color; - }); - }, { p5 }); + const shader = p5.baseColorShader().modify( + () => { + p5.getFinalColor(color => { + color = p5.lerp([1, 0, 0, 1], [0, 0, 1, 1], 0.5); + return color; + }); + }, + { p5 } + ); p5.background(0); p5.shader(shader); p5.noStroke(); @@ -1207,33 +1317,38 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre screenshot(); }); - visualTest('mix produces same result as lerp in strands', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - // mix directly, should produce identical output to lerp test above - const shader = p5.baseColorShader().modify(() => { - p5.getFinalColor((color) => { - color = p5.mix( - [1, 0, 0, 1], - [0, 0, 1, 1], - 0.5 - ); - return color; - }); - }, { p5 }); - p5.background(0); - p5.shader(shader); - p5.noStroke(); - p5.plane(50, 50); - screenshot(); - }); + visualTest( + 'mix produces same result as lerp in strands', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + // mix directly, should produce identical output to lerp test above + const shader = p5.baseColorShader().modify( + () => { + p5.getFinalColor(color => { + color = p5.mix([1, 0, 0, 1], [0, 0, 1, 1], 0.5); + return color; + }); + }, + { p5 } + ); + p5.background(0); + p5.shader(shader); + p5.noStroke(); + p5.plane(50, 50); + screenshot(); + } + ); visualTest('texCoord is available in getFinalColor', (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseColorShader().modify(() => { - p5.finalColor.begin(); - p5.finalColor.set([p5.finalColor.texCoord, 0, 1]); - p5.finalColor.end(); - }, { p5 }); + const shader = p5.baseColorShader().modify( + () => { + p5.finalColor.begin(); + p5.finalColor.set([p5.finalColor.texCoord, 0, 1]); + p5.finalColor.end(); + }, + { p5 } + ); p5.background(0); p5.shader(shader); p5.noStroke(); @@ -1242,29 +1357,38 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre }); visualSuite('auto-return for shader hooks', () => { - visualTest('auto-returns input struct when return is omitted', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseMaterialShader().modify(() => { - p5.getWorldInputs((inputs) => { - inputs.position.x += 10; - // No explicit return - should auto-return inputs - }); - }, { p5 }); - p5.background(255); - p5.noStroke(); - p5.shader(shader); - p5.sphere(20); - screenshot(); - }); + visualTest( + 'auto-returns input struct when return is omitted', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const shader = p5.baseMaterialShader().modify( + () => { + p5.getWorldInputs(inputs => { + inputs.position.x += 10; + // No explicit return - should auto-return inputs + }); + }, + { p5 } + ); + p5.background(255); + p5.noStroke(); + p5.shader(shader); + p5.sphere(20); + screenshot(); + } + ); visualTest('explicit return still works', (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseMaterialShader().modify(() => { - p5.getWorldInputs((inputs) => { - inputs.position.x += 10; - return inputs; // Explicit return should still work - }); - }, { p5 }); + const shader = p5.baseMaterialShader().modify( + () => { + p5.getWorldInputs(inputs => { + inputs.position.x += 10; + return inputs; // Explicit return should still work + }); + }, + { p5 } + ); p5.background(255); p5.noStroke(); p5.shader(shader); @@ -1274,12 +1398,15 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre visualTest('auto-return works with getObjectInputs', (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseMaterialShader().modify(() => { - p5.getObjectInputs((inputs) => { - inputs.position.x += 0.25; - // No explicit return - }); - }, { p5 }); + const shader = p5.baseMaterialShader().modify( + () => { + p5.getObjectInputs(inputs => { + inputs.position.x += 0.25; + // No explicit return + }); + }, + { p5 } + ); p5.background(255); p5.lights(); p5.fill('red'); @@ -1293,12 +1420,15 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre visualTest('auto-return works with getCameraInputs', (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseMaterialShader().modify(() => { - p5.getCameraInputs((inputs) => { - inputs.position.x += 10; - // No explicit return - }); - }, { p5 }); + const shader = p5.baseMaterialShader().modify( + () => { + p5.getCameraInputs(inputs => { + inputs.position.x += 10; + // No explicit return + }); + }, + { p5 } + ); p5.background(255); p5.lights(); p5.fill('red'); @@ -1310,34 +1440,43 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre screenshot(); }); - visualTest('auto-return preserves multiple property modifications', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseMaterialShader().modify(() => { - p5.getWorldInputs((inputs) => { - inputs.position.x += 5; - inputs.position.y += 5; - inputs.normal.x += 0.5; - inputs.normal = p5.normalize(inputs.normal); - // No explicit return - all modifications should be preserved - }); - }, { p5 }); - p5.background(255); - p5.lights(); - p5.fill('red'); - p5.noStroke(); - p5.shader(shader); - p5.sphere(20); - screenshot(); - }); + visualTest( + 'auto-return preserves multiple property modifications', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const shader = p5.baseMaterialShader().modify( + () => { + p5.getWorldInputs(inputs => { + inputs.position.x += 5; + inputs.position.y += 5; + inputs.normal.x += 0.5; + inputs.normal = p5.normalize(inputs.normal); + // No explicit return - all modifications should be preserved + }); + }, + { p5 } + ); + p5.background(255); + p5.lights(); + p5.fill('red'); + p5.noStroke(); + p5.shader(shader); + p5.sphere(20); + screenshot(); + } + ); visualTest('auto-return works with getPixelInputs', (p5, screenshot) => { p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseMaterialShader().modify(() => { - p5.getPixelInputs((inputs) => { - inputs.color = p5.vec4(1.0, 0.0, 0.0, 1.0); // Red - // No explicit return - }); - }, { p5 }); + const shader = p5.baseMaterialShader().modify( + () => { + p5.getPixelInputs(inputs => { + inputs.color = p5.vec4(1.0, 0.0, 0.0, 1.0); // Red + // No explicit return + }); + }, + { p5 } + ); p5.background(255); p5.noStroke(); p5.shader(shader); @@ -1346,7 +1485,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre }); }); - visualTest('Strands tutorial', function(p5, screenshot) { + visualTest('Strands tutorial', function (p5, screenshot) { // From Luke Plowden's Intro to Strands tutorial // https://beta.p5js.org/tutorials/intro-to-p5-strands/ @@ -1360,7 +1499,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre function semiSphere() { let id = p5.instanceIndex; - let theta = rand2([id, 0.1234]) * p5.TWO_PI + time / 100000; + let theta = rand2([id, 0.1234]) * p5.TWO_PI + time / 100000; let phi = rand2([id, 3.321]) * p5.PI + time / 50000; let r = skyRadius; @@ -1371,12 +1510,12 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre return [x, y, z]; } - p5.getWorldInputs((inputs) => { + p5.getWorldInputs(inputs => { inputs.position += semiSphere(); return inputs; }); - p5.getObjectInputs((inputs) => { + p5.getObjectInputs(inputs => { let size = 1 + 0.5 * p5.sin(time * 0.002 + p5.instanceIndex); inputs.position *= size; return inputs; @@ -1394,7 +1533,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre coord = p5.floor(coord * pixelSize) / pixelSize; let col = p5.getTexture(canvasContent, coord); - return col//[coord, 0, 1]; + return col; //[coord, 0, 1]; }); } @@ -1413,7 +1552,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre } p5.createCanvas(200, 200, p5.WEBGL); - const stars = p5.buildGeometry(() => p5.sphere(4, 4, 2)) + const stars = p5.buildGeometry(() => p5.sphere(4, 4, 2)); const originalImage = p5.createFramebuffer(); function fresnelShaderCallback({ p5 }) { @@ -1421,104 +1560,132 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre const fresnelBias = p5.uniformFloat(-0.1); const fresnelScale = p5.uniformFloat(2); - p5.getCameraInputs((inputs) => { + p5.getCameraInputs(inputs => { let n = p5.normalize(inputs.normal); let v = p5.normalize(-inputs.position); let base = 1.0 - p5.dot(n, v); let fresnel = fresnelScale * p5.pow(base, fresnelPower) + fresnelBias; - let col = p5.mix([0, 0, 0], [1, .5, .7], fresnel); + let col = p5.mix([0, 0, 0], [1, 0.5, 0.7], fresnel); inputs.color = [col, 1]; return inputs; }); } - const starShader = p5.baseMaterialShader().modify(starShaderCallback, { p5 }); - const starStrokeShader = p5.baseStrokeShader().modify(starShaderCallback, { p5 }) - const fresnelShader = p5.baseColorShader().modify(fresnelShaderCallback, { p5 }); - const bloomShader = p5.baseFilterShader().modify(bloomShaderCallback, { p5, originalImage }); - const pixelateShader = p5.baseFilterShader().modify(pixelateShaderCallback, { p5 }); + const starShader = p5 + .baseMaterialShader() + .modify(starShaderCallback, { p5 }); + const starStrokeShader = p5 + .baseStrokeShader() + .modify(starShaderCallback, { p5 }); + const fresnelShader = p5 + .baseColorShader() + .modify(fresnelShaderCallback, { p5 }); + const bloomShader = p5 + .baseFilterShader() + .modify(bloomShaderCallback, { p5, originalImage }); + const pixelateShader = p5 + .baseFilterShader() + .modify(pixelateShaderCallback, { p5 }); originalImage.begin(); p5.background(0); - p5.push() - p5.strokeWeight(2) - p5.stroke(255,0,0) - p5.fill(255,100, 150) - p5.strokeShader(starStrokeShader) + p5.push(); + p5.strokeWeight(2); + p5.stroke(255, 0, 0); + p5.fill(255, 100, 150); + p5.strokeShader(starStrokeShader); p5.shader(starShader); p5.model(stars, 100); - p5.pop() + p5.pop(); - p5.push() - p5.shader(fresnelShader) - p5.noStroke() + p5.push(); + p5.shader(fresnelShader); + p5.noStroke(); p5.sphere(30); p5.filter(pixelateShader); - p5.pop() + p5.pop(); originalImage.end(); - p5.imageMode(p5.CENTER) - p5.image(originalImage, 0, 0) + p5.imageMode(p5.CENTER); + p5.image(originalImage, 0, 0); - p5.filter(p5.BLUR, 5) + p5.filter(p5.BLUR, 5); p5.filter(bloomShader); screenshot(); }); - visualTest('setUniform with p5.Vector offsets position', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const myShader = p5.baseMaterialShader().modify(() => { - const uOffset = p5.uniformVec2('uOffset'); - p5.worldInputs.begin(); - p5.worldInputs.position.xy += uOffset; - p5.worldInputs.end(); - }, { p5 }); - p5.background(200); - p5.shader(myShader); - myShader.setUniform('uOffset', p5.createVector(10, -10)); - p5.noStroke(); - p5.fill('red'); - p5.circle(0, 0, 20); - screenshot(); - }); + visualTest( + 'setUniform with p5.Vector offsets position', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const myShader = p5.baseMaterialShader().modify( + () => { + const uOffset = p5.uniformVec2('uOffset'); + p5.worldInputs.begin(); + p5.worldInputs.position.xy += uOffset; + p5.worldInputs.end(); + }, + { p5 } + ); + p5.background(200); + p5.shader(myShader); + myShader.setUniform('uOffset', p5.createVector(10, -10)); + p5.noStroke(); + p5.fill('red'); + p5.circle(0, 0, 20); + screenshot(); + } + ); - visualTest('setUniform with p5.Color sets final color', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const myShader = p5.baseMaterialShader().modify(() => { - const uColor = p5.uniformVec4('uColor'); - p5.finalColor.begin(); - p5.finalColor.set(uColor); - p5.finalColor.end(); - }, { p5 }); - p5.background(200); - p5.shader(myShader); - myShader.setUniform('uColor', p5.color(0, 100, 200)); - p5.noStroke(); - p5.circle(0, 0, 30); - screenshot(); - }); + visualTest( + 'setUniform with p5.Color sets final color', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const myShader = p5.baseMaterialShader().modify( + () => { + const uColor = p5.uniformVec4('uColor'); + p5.finalColor.begin(); + p5.finalColor.set(uColor); + p5.finalColor.end(); + }, + { p5 } + ); + p5.background(200); + p5.shader(myShader); + myShader.setUniform('uColor', p5.color(0, 100, 200)); + p5.noStroke(); + p5.circle(0, 0, 30); + screenshot(); + } + ); - visualTest('hook returning a fresh struct (not the struct argument) applies modifications', (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); - const shader = p5.baseMaterialShader().modify(() => { - p5.worldInputs.begin(); - p5.worldInputs.set({ - position: p5.worldInputs.position.add([10, 0, 0]), - normal: p5.worldInputs.normal, - texCoord: p5.worldInputs.texCoord, - color: [1, 0, 0, 1], - }); - p5.worldInputs.end(); - }, { p5 }); - p5.background(0); - p5.noStroke(); - p5.shader(shader); - p5.plane(20, 20); - screenshot(); - }); + visualTest( + 'hook returning a fresh struct (not the struct argument) applies modifications', + (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const shader = p5.baseMaterialShader().modify( + () => { + p5.worldInputs.begin(); + p5.worldInputs.set({ + position: p5.worldInputs.position.add([10, 0, 0]), + normal: p5.worldInputs.normal, + texCoord: p5.worldInputs.texCoord, + color: [1, 0, 0, 1] + }); + p5.worldInputs.end(); + }, + { p5 } + ); + p5.background(0); + p5.noStroke(); + p5.shader(shader); + p5.plane(20, 20); + screenshot(); + } + ); }); visualSuite('setUniform', () => { @@ -1556,45 +1723,51 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre }); }); - visualSuite('Transforms', function() { - visualTest('translate() moves shapes in x and y axes', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background(200); - p5.noStroke(); + visualSuite('Transforms', function () { + visualTest( + 'translate() moves shapes in x and y axes', + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(200); + p5.noStroke(); - // Red circle at origin - p5.fill('red'); - p5.circle(0, 0, 10); + // Red circle at origin + p5.fill('red'); + p5.circle(0, 0, 10); - // Green circle translated by (15, 0) - p5.push(); - p5.translate(15, 0); - p5.fill('green'); - p5.circle(0, 0, 10); - p5.pop(); + // Green circle translated by (15, 0) + p5.push(); + p5.translate(15, 0); + p5.fill('green'); + p5.circle(0, 0, 10); + p5.pop(); - // Blue circle translated by (0, 15) - p5.push(); - p5.translate(0, 15); - p5.fill('blue'); - p5.circle(0, 0, 10); - p5.pop(); + // Blue circle translated by (0, 15) + p5.push(); + p5.translate(0, 15); + p5.fill('blue'); + p5.circle(0, 0, 10); + p5.pop(); - screenshot(); - }); + screenshot(); + } + ); - visualTest('rotate() rotates shapes around z-axis', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background(200); - p5.noStroke(); - p5.fill('red'); - p5.rectMode(p5.CENTER); - p5.rotate(p5.PI / 4); - p5.rect(0, 0, 30, 30); - screenshot(); - }); + visualTest( + 'rotate() rotates shapes around z-axis', + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(200); + p5.noStroke(); + p5.fill('red'); + p5.rectMode(p5.CENTER); + p5.rotate(p5.PI / 4); + p5.rect(0, 0, 30, 30); + screenshot(); + } + ); - visualTest('scale() uniformly scales shapes', function(p5, screenshot) { + visualTest('scale() uniformly scales shapes', function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); p5.background(200); p5.noStroke(); @@ -1614,8 +1787,8 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre }); }); - visualSuite('media assets', function() { - visualTest('drawing gifs', async function(p5, screenshot) { + visualSuite('media assets', function () { + visualTest('drawing gifs', async function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); const gif = await p5.loadImage('/test/unit/assets/nyan_cat.gif'); p5.imageMode(p5.CENTER); @@ -1623,21 +1796,24 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre screenshot(); }); - visualTest('drawing gifs after a time delay', async function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - const gif = await p5.loadImage('/test/unit/assets/nyan_cat.gif'); - p5.imageMode(p5.CENTER); - p5.image(gif, 0, 0); - p5.clear() - // Simulate waiting for successive draw calls - p5._lastRealFrameTime += 300; - p5.image(gif, 0, 0); - screenshot(); - }); + visualTest( + 'drawing gifs after a time delay', + async function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + const gif = await p5.loadImage('/test/unit/assets/nyan_cat.gif'); + p5.imageMode(p5.CENTER); + p5.image(gif, 0, 0); + p5.clear(); + // Simulate waiting for successive draw calls + p5._lastRealFrameTime += 300; + p5.image(gif, 0, 0); + screenshot(); + } + ); }); - visualSuite('2D Shapes', function() { - visualTest('rect() rounded into a circle', function(p5, screenshot) { + visualSuite('2D Shapes', function () { + visualTest('rect() rounded into a circle', function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); p5.background(255); p5.noStroke(); @@ -1646,7 +1822,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre screenshot(); }); - visualTest('TRIANGLE_FAN with per-vertex fills', function(p5, screenshot) { + visualTest('TRIANGLE_FAN with per-vertex fills', function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); p5.background(255); p5.beginShape(p5.TRIANGLE_FAN); @@ -1656,50 +1832,41 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre const r = 20; p5.fill('blue'); for (let i = 0; i <= n; i++) { - const angle = i/n * p5.TWO_PI; - p5.vertex(r*p5.cos(angle), r*p5.sin(angle)); + const angle = (i / n) * p5.TWO_PI; + p5.vertex(r * p5.cos(angle), r * p5.sin(angle)); } p5.endShape(); screenshot(); }); - visualTest('TRIANGLE_FAN in p5.Geometry with per-vertex fills', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background(255); - const geom = p5.buildGeometry(() => { - p5.beginShape(p5.TRIANGLE_FAN); - p5.fill('red'); - p5.vertex(0, 0); - const n = 10; - const r = 20; - p5.fill('blue'); - for (let i = 0; i <= n; i++) { - const angle = i/n * p5.TWO_PI; - p5.vertex(r*p5.cos(angle), r*p5.sin(angle)); - } - p5.endShape(); - }); - p5.model(geom); - screenshot(); - }); - - visualTest('TRIANGLE_STRIP with per-vertex fills', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background(255); - p5.beginShape(p5.TRIANGLE_STRIP); - const n = 6; - for (let i = 0; i < n; i++) { - p5.fill(i % 2 === 0 ? 'red' : 'blue'); - p5.vertex(p5.map(i, 0, n - 1, -20, 20), i % 2 === 0 ? -10 : 10); + visualTest( + 'TRIANGLE_FAN in p5.Geometry with per-vertex fills', + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(255); + const geom = p5.buildGeometry(() => { + p5.beginShape(p5.TRIANGLE_FAN); + p5.fill('red'); + p5.vertex(0, 0); + const n = 10; + const r = 20; + p5.fill('blue'); + for (let i = 0; i <= n; i++) { + const angle = (i / n) * p5.TWO_PI; + p5.vertex(r * p5.cos(angle), r * p5.sin(angle)); + } + p5.endShape(); + }); + p5.model(geom); + screenshot(); } - p5.endShape(); - screenshot(); - }); + ); - visualTest('TRIANGLE_STRIP in p5.Geometry with per-vertex fills', function(p5, screenshot) { - p5.createCanvas(50, 50, p5.WEBGL); - p5.background(255); - const geom = p5.buildGeometry(() => { + visualTest( + 'TRIANGLE_STRIP with per-vertex fills', + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(255); p5.beginShape(p5.TRIANGLE_STRIP); const n = 6; for (let i = 0; i < n; i++) { @@ -1707,14 +1874,32 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre p5.vertex(p5.map(i, 0, n - 1, -20, 20), i % 2 === 0 ? -10 : 10); } p5.endShape(); - }); - p5.model(geom); - screenshot(); - }); + screenshot(); + } + ); + + visualTest( + 'TRIANGLE_STRIP in p5.Geometry with per-vertex fills', + function (p5, screenshot) { + p5.createCanvas(50, 50, p5.WEBGL); + p5.background(255); + const geom = p5.buildGeometry(() => { + p5.beginShape(p5.TRIANGLE_STRIP); + const n = 6; + for (let i = 0; i < n; i++) { + p5.fill(i % 2 === 0 ? 'red' : 'blue'); + p5.vertex(p5.map(i, 0, n - 1, -20, 20), i % 2 === 0 ? -10 : 10); + } + p5.endShape(); + }); + p5.model(geom); + screenshot(); + } + ); }); - visualSuite('3D Primitives', function() { - visualTest('cylinder() renders correctly', function(p5, screenshot) { + visualSuite('3D Primitives', function () { + visualTest('cylinder() renders correctly', function (p5, screenshot) { p5.createCanvas(100, 100, p5.WEBGL); p5.background(255); @@ -1731,7 +1916,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre screenshot(); }); - visualTest('cone() renders correctly', function(p5, screenshot) { + visualTest('cone() renders correctly', function (p5, screenshot) { p5.createCanvas(100, 100, p5.WEBGL); p5.background(255); @@ -1748,24 +1933,27 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre screenshot(); }); - visualTest('ellipsoid() renders with non-uniform radii', function(p5, screenshot) { - p5.createCanvas(100, 100, p5.WEBGL); - p5.background(255); + visualTest( + 'ellipsoid() renders with non-uniform radii', + function (p5, screenshot) { + p5.createCanvas(100, 100, p5.WEBGL); + p5.background(255); - p5.ambientLight(100); - p5.directionalLight(255, 255, 255, 0, 0, -1); + p5.ambientLight(100); + p5.directionalLight(255, 255, 255, 0, 0, -1); - p5.noStroke(); - p5.fill(200); + p5.noStroke(); + p5.fill(200); - p5.rotateX(p5.PI / 6); - p5.rotateY(p5.PI / 4); + p5.rotateX(p5.PI / 6); + p5.rotateY(p5.PI / 4); - p5.ellipsoid(20, 30, 40); - screenshot(); - }); + p5.ellipsoid(20, 30, 40); + screenshot(); + } + ); - visualTest('torus() renders correctly', function(p5, screenshot) { + visualTest('torus() renders correctly', function (p5, screenshot) { p5.createCanvas(100, 100, p5.WEBGL); p5.background(255); @@ -1783,82 +1971,85 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre }); }); - visualSuite('Tessellation', function() { - visualTest('Handles nearly identical consecutive vertices', function(p5, screenshot) { - p5.createCanvas(400, 400, p5.WEBGL); - - const contours = [ - [ - [-3.8642425537109375, -6.120738636363637, 0], - [3.2025188099254267, -6.120738636363637, 0], - [3.2025188099254267, -4.345170454545455, 0], - [-3.8642425537109375, -4.345170454545455, 0], - [-3.8642425537109375, -6.120738636363637, 0] - ], - [ - [-1.8045834628018462, 4.177556818181818, 0], - [-1.8045834628018462, -9.387784090909093, 0], - [0.29058699174360836, -9.387784090909093, 0], - [0.2905869917436083, 3.609374411367136, 0], - [0.31044303036623855, 4.068235883781435, 0], - [0.38522861430307975, 4.522728865422799, 0], - [0.548044378107245, 4.941051136363637, 0], - [0.8364672032828204, 5.2932224887960775, 0], - [1.2227602871981542, 5.526988636363637, 0], - [1.6572258237923885, 5.634502949876295, 0], - [2.101666537198154, 5.669034090909091, 0], - [2.6695604948237173, 5.633568761673102, 0], - [3.0249619917436084, 5.5625, 0], - [3.4510983553799726, 7.4446022727272725, 0], - [2.8568950819856695, 7.613138889205699, 0], - [2.3751340936529037, 7.676962586830456, 0], - [1.8892600236717598, 7.693181792704519, 0], - [1.2922705720786674, 7.649533731133848, 0], - [0.7080836288276859, 7.519788939617751, 0], - [0.14854153719815422, 7.311434659090909, 0], - [-0.38643934048179873, 7.00959666478984, 0], - [-0.858113258144025, 6.61653855366859, 0], - [-1.25415732643821, 6.1484375, 0], - [-1.5108595282965422, 5.697682732328092, 0], - [-1.6824918355513252, 5.207533878495854, 0], - [-1.7762971052870198, 4.695933154267308, 0], - [-1.8045834628018462, 4.177556818181818, 0] - ] - ]; - - p5.background('red'); - p5.push(); - p5.stroke(0); - p5.fill('#EEE'); - p5.scale(15); - p5.beginShape(); - for (const contour of contours) { - p5.beginContour(); - for (const v of contour) { - p5.vertex(...v); + visualSuite('Tessellation', function () { + visualTest( + 'Handles nearly identical consecutive vertices', + function (p5, screenshot) { + p5.createCanvas(400, 400, p5.WEBGL); + + const contours = [ + [ + [-3.8642425537109375, -6.120738636363637, 0], + [3.2025188099254267, -6.120738636363637, 0], + [3.2025188099254267, -4.345170454545455, 0], + [-3.8642425537109375, -4.345170454545455, 0], + [-3.8642425537109375, -6.120738636363637, 0] + ], + [ + [-1.8045834628018462, 4.177556818181818, 0], + [-1.8045834628018462, -9.387784090909093, 0], + [0.29058699174360836, -9.387784090909093, 0], + [0.2905869917436083, 3.609374411367136, 0], + [0.31044303036623855, 4.068235883781435, 0], + [0.38522861430307975, 4.522728865422799, 0], + [0.548044378107245, 4.941051136363637, 0], + [0.8364672032828204, 5.2932224887960775, 0], + [1.2227602871981542, 5.526988636363637, 0], + [1.6572258237923885, 5.634502949876295, 0], + [2.101666537198154, 5.669034090909091, 0], + [2.6695604948237173, 5.633568761673102, 0], + [3.0249619917436084, 5.5625, 0], + [3.4510983553799726, 7.4446022727272725, 0], + [2.8568950819856695, 7.613138889205699, 0], + [2.3751340936529037, 7.676962586830456, 0], + [1.8892600236717598, 7.693181792704519, 0], + [1.2922705720786674, 7.649533731133848, 0], + [0.7080836288276859, 7.519788939617751, 0], + [0.14854153719815422, 7.311434659090909, 0], + [-0.38643934048179873, 7.00959666478984, 0], + [-0.858113258144025, 6.61653855366859, 0], + [-1.25415732643821, 6.1484375, 0], + [-1.5108595282965422, 5.697682732328092, 0], + [-1.6824918355513252, 5.207533878495854, 0], + [-1.7762971052870198, 4.695933154267308, 0], + [-1.8045834628018462, 4.177556818181818, 0] + ] + ]; + + p5.background('red'); + p5.push(); + p5.stroke(0); + p5.fill('#EEE'); + p5.scale(15); + p5.beginShape(); + for (const contour of contours) { + p5.beginContour(); + for (const v of contour) { + p5.vertex(...v); + } + p5.endContour(); } - p5.endContour(); - } - p5.endShape(); + p5.endShape(); - p5.stroke(0, 255, 0); - p5.strokeWeight(5); - p5.beginShape(p5.POINTS); - for (const contour of contours) { - for (const v of contour) { - p5.vertex(...v); + p5.stroke(0, 255, 0); + p5.strokeWeight(5); + p5.beginShape(p5.POINTS); + for (const contour of contours) { + for (const v of contour) { + p5.vertex(...v); + } } - } - p5.endShape(); - p5.pop(); + p5.endShape(); + p5.pop(); - screenshot(); - }); + screenshot(); + } + ); }); visualTest( 'user-set uSampler on custom shader is not overridden', - function(p5, screenshot) { + function (p5, screenshot) { p5.createCanvas(50, 50, p5.WEBGL); const myShader = p5.createFilterShader(`precision highp float; diff --git a/test/unit/visual/cases/webgpu.js b/test/unit/visual/cases/webgpu.js index c789520258..864ce43290 100644 --- a/test/unit/visual/cases/webgpu.js +++ b/test/unit/visual/cases/webgpu.js @@ -1,18 +1,18 @@ -import { vi } from "vitest"; -import p5 from "../../../../src/app"; -import { visualSuite, visualTest } from "../visualTest"; -import rendererWebGPU from "../../../../src/webgpu/p5.RendererWebGPU"; +import { vi } from 'vitest'; +import p5 from '../../../../src/app'; +import { visualSuite, visualTest } from '../visualTest'; +import rendererWebGPU from '../../../../src/webgpu/p5.RendererWebGPU'; p5.registerAddon(rendererWebGPU); -visualSuite("WebGPU", function () { - visualSuite("Shaders", function () { +visualSuite('WebGPU', function () { + visualSuite('Shaders', function () { visualTest( - "The color shader runs successfully", + 'The color shader runs successfully', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); - p5.background("white"); - for (const [i, color] of ["red", "lime", "blue"].entries()) { + p5.background('white'); + for (const [i, color] of ['red', 'lime', 'blue'].entries()) { p5.push(); p5.rotate(p5.TWO_PI * (i / 3)); p5.fill(color); @@ -22,15 +22,15 @@ visualSuite("WebGPU", function () { p5.pop(); } await screenshot(); - }, + } ); visualTest( - "The stroke shader runs successfully", + 'The stroke shader runs successfully', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); - p5.background("white"); - for (const [i, color] of ["red", "lime", "blue"].entries()) { + p5.background('white'); + for (const [i, color] of ['red', 'lime', 'blue'].entries()) { p5.push(); p5.rotate(p5.TWO_PI * (i / 3)); p5.translate(15, 0); @@ -40,20 +40,20 @@ visualSuite("WebGPU", function () { p5.pop(); } await screenshot(); - }, + } ); visualTest( - "The material shader runs successfully", + 'The material shader runs successfully', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); - p5.background("white"); + p5.background('white'); p5.ambientLight(50); p5.directionalLight(100, 100, 100, 0, 1, -1); p5.pointLight(155, 155, 155, 0, -200, 500); p5.specularMaterial(255); p5.shininess(300); - for (const [i, color] of ["red", "lime", "blue"].entries()) { + for (const [i, color] of ['red', 'lime', 'blue'].entries()) { p5.push(); p5.rotate(p5.TWO_PI * (i / 3)); p5.fill(color); @@ -63,37 +63,37 @@ visualSuite("WebGPU", function () { p5.pop(); } await screenshot(); - }, + } ); - visualTest("Shader hooks can be used", async function (p5, screenshot) { + visualTest('Shader hooks can be used', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const myFill = p5.baseMaterialShader().modify({ - "Vertex getWorldInputs": `(inputs: Vertex) { + 'Vertex getWorldInputs': `(inputs: Vertex) { var result = inputs; result.position.y += 10.0 * sin(inputs.position.x * 0.25); return result; - }`, + }` }); const myStroke = p5.baseStrokeShader().modify({ - "StrokeVertex getWorldInputs": `(inputs: StrokeVertex) { + 'StrokeVertex getWorldInputs': `(inputs: StrokeVertex) { var result = inputs; result.position.y += 10.0 * sin(inputs.position.x * 0.25); return result; - }`, + }` }); - p5.background("black"); + p5.background('black'); p5.shader(myFill); p5.strokeShader(myStroke); - p5.fill("red"); - p5.stroke("white"); + p5.fill('red'); + p5.stroke('white'); p5.strokeWeight(5); p5.circle(0, 0, 30); await screenshot(); }); visualTest( - "Textures in the material shader work", + 'Textures in the material shader work', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const tex = p5.createImage(50, 50); @@ -112,18 +112,21 @@ visualSuite("WebGPU", function () { p5.plane(p5.width, p5.height); await screenshot(); - }, + } ); - visualTest('Instanced rendering', async function(p5, screenshot) { + visualTest('Instanced rendering', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const model = p5.buildGeometry(() => p5.sphere(5)); - const shader = p5.baseMaterialShader().modify(() => { - p5.getWorldInputs((inputs) => { - inputs.position += (p5.instanceIndex - 1) * 15 - return inputs; - }); - }, { p5 }); + const shader = p5.baseMaterialShader().modify( + () => { + p5.getWorldInputs(inputs => { + inputs.position += (p5.instanceIndex - 1) * 15; + return inputs; + }); + }, + { p5 } + ); p5.noStroke(); p5.fill(0); p5.shader(shader); @@ -131,7 +134,7 @@ visualSuite("WebGPU", function () { await screenshot(); }); - visualTest('Strands tutorial', async function(p5, screenshot) { + visualTest('Strands tutorial', async function (p5, screenshot) { // From Luke Plowden's Intro to Strands tutorial // https://beta.p5js.org/tutorials/intro-to-p5-strands/ @@ -145,7 +148,7 @@ visualSuite("WebGPU", function () { function semiSphere() { let id = p5.instanceIndex; - let theta = rand2([id, 0.1234]) * p5.TWO_PI + time / 100000; + let theta = rand2([id, 0.1234]) * p5.TWO_PI + time / 100000; let phi = rand2([id, 3.321]) * p5.PI + time / 50000; let r = skyRadius; @@ -156,12 +159,12 @@ visualSuite("WebGPU", function () { return [x, y, z]; } - p5.getWorldInputs((inputs) => { + p5.getWorldInputs(inputs => { inputs.position += semiSphere(); return inputs; }); - p5.getObjectInputs((inputs) => { + p5.getObjectInputs(inputs => { let size = 1 + 0.5 * p5.sin(time * 0.002 + p5.instanceIndex); inputs.position *= size; return inputs; @@ -179,7 +182,7 @@ visualSuite("WebGPU", function () { coord = p5.floor(coord * pixelSize) / pixelSize; let col = p5.getTexture(canvasContent, coord); - return col//[coord, 0, 1]; + return col; //[coord, 0, 1]; }); } @@ -198,7 +201,7 @@ visualSuite("WebGPU", function () { } await p5.createCanvas(200, 200, p5.WEBGPU); - const stars = p5.buildGeometry(() => p5.sphere(4, 4, 2)) + const stars = p5.buildGeometry(() => p5.sphere(4, 4, 2)); const originalImage = p5.createFramebuffer(); function fresnelShaderCallback({ p5 }) { @@ -206,228 +209,282 @@ visualSuite("WebGPU", function () { const fresnelBias = p5.uniformFloat(-0.1); const fresnelScale = p5.uniformFloat(2); - p5.getCameraInputs((inputs) => { + p5.getCameraInputs(inputs => { let n = p5.normalize(inputs.normal); let v = p5.normalize(-inputs.position); let base = 1.0 - p5.dot(n, v); let fresnel = fresnelScale * p5.pow(base, fresnelPower) + fresnelBias; - let col = p5.mix([0, 0, 0], [1, .5, .7], fresnel); + let col = p5.mix([0, 0, 0], [1, 0.5, 0.7], fresnel); inputs.color = [col, 1]; return inputs; }); } - const starShader = p5.baseMaterialShader().modify(starShaderCallback, { p5 }); - const starStrokeShader = p5.baseStrokeShader().modify(starShaderCallback, { p5 }) - const fresnelShader = p5.baseColorShader().modify(fresnelShaderCallback, { p5 }); - const bloomShader = p5.baseFilterShader().modify(bloomShaderCallback, { p5, originalImage }); - const pixelateShader = p5.baseFilterShader().modify(pixelateShaderCallback, { p5 }); + const starShader = p5 + .baseMaterialShader() + .modify(starShaderCallback, { p5 }); + const starStrokeShader = p5 + .baseStrokeShader() + .modify(starShaderCallback, { p5 }); + const fresnelShader = p5 + .baseColorShader() + .modify(fresnelShaderCallback, { p5 }); + const bloomShader = p5 + .baseFilterShader() + .modify(bloomShaderCallback, { p5, originalImage }); + const pixelateShader = p5 + .baseFilterShader() + .modify(pixelateShaderCallback, { p5 }); originalImage.begin(); p5.background(0); - p5.push() - p5.strokeWeight(2) - p5.stroke(255,0,0) - p5.fill(255,100, 150) - p5.strokeShader(starStrokeShader) + p5.push(); + p5.strokeWeight(2); + p5.stroke(255, 0, 0); + p5.fill(255, 100, 150); + p5.strokeShader(starStrokeShader); p5.shader(starShader); p5.model(stars, 100); - p5.pop() + p5.pop(); - p5.push() - p5.shader(fresnelShader) - p5.noStroke() + p5.push(); + p5.shader(fresnelShader); + p5.noStroke(); p5.sphere(30); p5.filter(pixelateShader); - p5.pop() + p5.pop(); originalImage.end(); - p5.imageMode(p5.CENTER) - p5.image(originalImage, 0, 0) + p5.imageMode(p5.CENTER); + p5.image(originalImage, 0, 0); - p5.filter(p5.BLUR, 5) + p5.filter(p5.BLUR, 5); p5.filter(bloomShader); await screenshot(); }); - visualTest('filter shaders with flat API', async function(p5, screenshot) { + visualTest('filter shaders with flat API', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); p5.background(255); p5.noStroke(); p5.fill(0); p5.circle(0, 0, 20); - const invert = p5.buildFilterShader(({ p5 }) => { - p5.filterColor.begin(); - const regular = p5.getTexture( - p5.filterColor.canvasContent, - p5.filterColor.texCoord - ); - const inverted = [1 - regular.rgb, regular.a]; - p5.filterColor.set(inverted); - p5.filterColor.end(); - }, { p5 }); - p5.filter(invert); - await screenshot(); - }); - - visualTest('filter shaders can sample a texture inside a conditional branch', async function(p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - p5.background(255); - p5.noStroke(); - p5.fill(0); - p5.circle(0, 0, 20); - // This shader only samples the texture for pixels in the left half of the - // canvas, exercising getTexture() inside a non-uniform conditional - const conditionalInvert = p5.buildFilterShader(({ p5 }) => { - p5.filterColor.begin(); - if (p5.filterColor.texCoord.x < 0.5) { - const col = p5.getTexture( + const invert = p5.buildFilterShader( + ({ p5 }) => { + p5.filterColor.begin(); + const regular = p5.getTexture( p5.filterColor.canvasContent, p5.filterColor.texCoord ); - p5.filterColor.set([1 - col.rgb, col.a]); - } else { - p5.filterColor.set([0, 0, 1, 1]); - } - p5.filterColor.end(); - }, { p5 }); - p5.filter(conditionalInvert); + const inverted = [1 - regular.rgb, regular.a]; + p5.filterColor.set(inverted); + p5.filterColor.end(); + }, + { p5 } + ); + p5.filter(invert); await screenshot(); }); - visualTest('instanceID in fragment hook colors instances (WebGPU)', async function(p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - const numInstances = 4; - const shader = p5.baseMaterialShader().modify(() => { - // Vertex hook: position instances in a horizontal row - p5.getWorldInputs((inputs) => { - const id = p5.instanceIndex; - const spacing = 12; - const offset = (id - (numInstances - 1) / 2.0) * spacing; - inputs.position.x += offset; - return inputs; - }); - // Fragment hook: color each instance based on instanceID - p5.getFinalColor((color) => { - const id = p5.instanceIndex; - const t = id / (numInstances - 1.0); - color = [t, t, t, 1]; - return color; - }); - }, { p5, numInstances }); - p5.background(128); - p5.noStroke(); - p5.shader(shader); - const obj = p5.buildGeometry(() => p5.circle(0, 0, 10)); - p5.model(obj, numInstances); - await screenshot(); - }); + visualTest( + 'filter shaders can sample a texture inside a conditional branch', + async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + p5.background(255); + p5.noStroke(); + p5.fill(0); + p5.circle(0, 0, 20); + // This shader only samples the texture for pixels in the left half of the + // canvas, exercising getTexture() inside a non-uniform conditional + const conditionalInvert = p5.buildFilterShader( + ({ p5 }) => { + p5.filterColor.begin(); + if (p5.filterColor.texCoord.x < 0.5) { + const col = p5.getTexture( + p5.filterColor.canvasContent, + p5.filterColor.texCoord + ); + p5.filterColor.set([1 - col.rgb, col.a]); + } else { + p5.filterColor.set([0, 0, 1, 1]); + } + p5.filterColor.end(); + }, + { p5 } + ); + p5.filter(conditionalInvert); + await screenshot(); + } + ); - visualTest('random() colors a basic shader (WebGPU)', async function(p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - const shader = p5.baseColorShader().modify(() => { - p5.randomSeed(12); - p5.getFinalColor((color) => { - const value = p5.random(0.2, 0.9); - color = [value, value, value, 1]; - return color; - }); - }, { p5 }); - p5.background(0); - p5.noStroke(); - p5.shader(shader); - p5.plane(50, 50); - await screenshot(); - }); + visualTest( + 'instanceID in fragment hook colors instances (WebGPU)', + async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + const numInstances = 4; + const shader = p5.baseMaterialShader().modify( + () => { + // Vertex hook: position instances in a horizontal row + p5.getWorldInputs(inputs => { + const id = p5.instanceIndex; + const spacing = 12; + const offset = (id - (numInstances - 1) / 2.0) * spacing; + inputs.position.x += offset; + return inputs; + }); + // Fragment hook: color each instance based on instanceID + p5.getFinalColor(color => { + const id = p5.instanceIndex; + const t = id / (numInstances - 1.0); + color = [t, t, t, 1]; + return color; + }); + }, + { p5, numInstances } + ); + p5.background(128); + p5.noStroke(); + p5.shader(shader); + const obj = p5.buildGeometry(() => p5.circle(0, 0, 10)); + p5.model(obj, numInstances); + await screenshot(); + } + ); - visualTest('random() in a fragment loop averages to gray (WebGPU)', async function(p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - const shader = p5.baseMaterialShader().modify(() => { - p5.randomSeed(7); - p5.getPixelInputs(inputs => { - let sum = p5.float(0.0); - for (let i = 0; i < 20; i++) { - sum = sum + p5.random(); - } - const avg = sum / 20; - inputs.color = [avg, avg, avg, 1.0]; - return inputs; - }); - }, { p5 }); + visualTest( + 'random() colors a basic shader (WebGPU)', + async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + const shader = p5.baseColorShader().modify( + () => { + p5.randomSeed(12); + p5.getFinalColor(color => { + const value = p5.random(0.2, 0.9); + color = [value, value, value, 1]; + return color; + }); + }, + { p5 } + ); + p5.background(0); + p5.noStroke(); + p5.shader(shader); + p5.plane(50, 50); + await screenshot(); + } + ); + visualTest( + 'random() in a fragment loop averages to gray (WebGPU)', + async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + const shader = p5.baseMaterialShader().modify( + () => { + p5.randomSeed(7); + p5.getPixelInputs(inputs => { + let sum = p5.float(0.0); + for (let i = 0; i < 20; i++) { + sum = sum + p5.random(); + } + const avg = sum / 20; + inputs.color = [avg, avg, avg, 1.0]; + return inputs; + }); + }, + { p5 } + ); + + p5.background(0); + p5.noStroke(); + p5.shader(shader); + p5.plane(50, 50); + await screenshot(); + } + ); + + visualTest( + 'hook returning a fresh struct (not the struct argument) applies modifications', + async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + const shader = p5.baseMaterialShader().modify( + () => { + p5.worldInputs.begin(); + p5.worldInputs.set({ + position: p5.worldInputs.position.add([10, 0, 0]), + normal: p5.worldInputs.normal, + texCoord: p5.worldInputs.texCoord, + color: [1, 0, 0, 1] + }); + p5.worldInputs.end(); + }, + { p5 } + ); + p5.background(0); + p5.noStroke(); + p5.shader(shader); + p5.plane(20, 20); + await screenshot(); + } + ); + }); + + visualTest( + 'randomGaussian() colors a basic shader (WebGPU)', + async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + const shader = p5.baseColorShader().modify( + () => { + p5.randomSeed(12); + p5.getFinalColor(color => { + const value = p5.randomGaussian(0.5, 0.1); + color = [value, value, value, 1]; + return color; + }); + }, + { p5 } + ); p5.background(0); p5.noStroke(); p5.shader(shader); p5.plane(50, 50); await screenshot(); - }); + } + ); - visualTest('hook returning a fresh struct (not the struct argument) applies modifications', async function(p5, screenshot) { + visualTest( + 'randomGaussian() in a fragment loop averages to the mean (WebGPU)', + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); - const shader = p5.baseMaterialShader().modify(() => { - p5.worldInputs.begin(); - p5.worldInputs.set({ - position: p5.worldInputs.position.add([10, 0, 0]), - normal: p5.worldInputs.normal, - texCoord: p5.worldInputs.texCoord, - color: [1, 0, 0, 1], - }); - p5.worldInputs.end(); - }, { p5 }); + const shader = p5.baseMaterialShader().modify( + () => { + p5.randomSeed(7); + p5.getPixelInputs(inputs => { + let sum = p5.float(0.0); + for (let i = 0; i < 20; i++) { + sum = sum + p5.randomGaussian(0.5, 0.2); + } + const avg = sum / 20; + inputs.color = [avg, avg, avg, 1.0]; + return inputs; + }); + }, + { p5 } + ); p5.background(0); p5.noStroke(); p5.shader(shader); - p5.plane(20, 20); + p5.plane(50, 50); await screenshot(); - }); - }); - - visualTest('randomGaussian() colors a basic shader (WebGPU)', async function(p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - const shader = p5.baseColorShader().modify(() => { - p5.randomSeed(12); - p5.getFinalColor((color) => { - const value = p5.randomGaussian(0.5, 0.1); - color = [value, value, value, 1]; - return color; - }); - }, { p5 }); - p5.background(0); - p5.noStroke(); - p5.shader(shader); - p5.plane(50, 50); - await screenshot(); -}); - -visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', async function(p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - const shader = p5.baseMaterialShader().modify(() => { - p5.randomSeed(7); - p5.getPixelInputs(inputs => { - let sum = p5.float(0.0); - for (let i = 0; i < 20; i++) { - sum = sum + p5.randomGaussian(0.5, 0.2); - } - const avg = sum / 20; - inputs.color = [avg, avg, avg, 1.0]; - return inputs; - }); - }, { p5 }); - p5.background(0); - p5.noStroke(); - p5.shader(shader); - p5.plane(50, 50); - await screenshot(); -}); - + } + ); - visualSuite('filters', function() { - const setupSketch = async (p5) => { + visualSuite('filters', function () { + const setupSketch = async p5 => { await p5.createCanvas(50, 50, p5.WEBGPU); - p5.translate(-p5.width/2, -p5.height/2); + p5.translate(-p5.width / 2, -p5.height / 2); p5.clear(); p5.noStroke(); p5.fill('red'); @@ -442,61 +499,70 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', p5.endShape(); }; - visualTest('It can apply GRAY', async function(p5, screenshot) { + visualTest('It can apply GRAY', async function (p5, screenshot) { await setupSketch(p5); p5.filter(p5.GRAY); await screenshot(); }); - visualTest('It can apply INVERT', async function(p5, screenshot) { + visualTest('It can apply INVERT', async function (p5, screenshot) { await setupSketch(p5); p5.filter(p5.INVERT); await screenshot(); }); - visualTest('It can apply THRESHOLD', async function(p5, screenshot) { + visualTest('It can apply THRESHOLD', async function (p5, screenshot) { await setupSketch(p5); p5.filter(p5.THRESHOLD); await screenshot(); }); - visualTest('It can apply THRESHOLD with a value', async function(p5, screenshot) { - await setupSketch(p5); - p5.filter(p5.THRESHOLD, 0.8); - await screenshot(); - }); - visualTest('It can apply POSTERIZE', async function(p5, screenshot) { + visualTest( + 'It can apply THRESHOLD with a value', + async function (p5, screenshot) { + await setupSketch(p5); + p5.filter(p5.THRESHOLD, 0.8); + await screenshot(); + } + ); + visualTest('It can apply POSTERIZE', async function (p5, screenshot) { await setupSketch(p5); p5.filter(p5.THRESHOLD); await screenshot(); }); - visualTest('It can apply POSTERIZE with a value', async function(p5, screenshot) { - await setupSketch(p5); - p5.filter(p5.THRESHOLD, 2); - await screenshot(); - }); - visualTest('It can apply BLUR', async function(p5, screenshot) { + visualTest( + 'It can apply POSTERIZE with a value', + async function (p5, screenshot) { + await setupSketch(p5); + p5.filter(p5.THRESHOLD, 2); + await screenshot(); + } + ); + visualTest('It can apply BLUR', async function (p5, screenshot) { await setupSketch(p5); p5.filter(p5.BLUR, 5); await screenshot(); }); - visualTest('It can apply BLUR with a value', async function(p5, screenshot) { - await setupSketch(p5); - p5.filter(p5.BLUR, 10); - await screenshot(); - }); - visualTest('It can apply ERODE (4x)', async function(p5, screenshot) { + visualTest( + 'It can apply BLUR with a value', + async function (p5, screenshot) { + await setupSketch(p5); + p5.filter(p5.BLUR, 10); + await screenshot(); + } + ); + visualTest('It can apply ERODE (4x)', async function (p5, screenshot) { await setupSketch(p5); for (let i = 0; i < 4; i++) p5.filter(p5.ERODE); await screenshot(); }); - visualTest('It can apply DILATE (4x)', async function(p5, screenshot) { + visualTest('It can apply DILATE (4x)', async function (p5, screenshot) { await setupSketch(p5); for (let i = 0; i < 4; i++) p5.filter(p5.DILATE); await screenshot(); }); }); - visualSuite("Canvas Resizing", function () { + visualSuite('Canvas Resizing', function () { visualTest( - "Main canvas drawing after resize", + 'Main canvas drawing after resize', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Resize the canvas @@ -507,13 +573,13 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', p5.noStroke(); p5.circle(0, 0, 20); await screenshot(); - }, + } ); }); - visualSuite("Framebuffers", function () { + visualSuite('Framebuffers', function () { visualTest( - "Basic framebuffer draw to canvas", + 'Basic framebuffer draw to canvas', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Create a framebuffer @@ -534,11 +600,11 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', p5.plane(25, 25); await screenshot(); - }, + } ); visualTest( - "Framebuffer with different sizes", + 'Framebuffer with different sizes', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Create two different sized framebuffers @@ -578,10 +644,10 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', p5.pop(); await screenshot(); - }, + } ); - visualTest("Auto-sized framebuffer", async function (p5, screenshot) { + visualTest('Auto-sized framebuffer', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Create auto-sized framebuffer (should match canvas size) const fbo = p5.createFramebuffer(); @@ -589,7 +655,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', // Draw to the framebuffer fbo.draw(() => { p5.background(0); - p5.translate(-fbo.width / 2, -fbo.height / 2) + p5.translate(-fbo.width / 2, -fbo.height / 2); p5.stroke(255); p5.strokeWeight(2); p5.noFill(); @@ -614,7 +680,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', }); visualTest( - "Auto-sized framebuffer after canvas resize", + 'Auto-sized framebuffer after canvas resize', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Create auto-sized framebuffer @@ -626,7 +692,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', // Draw to the framebuffer after resize fbo.draw(() => { p5.background(100, 0, 100); - p5.translate(-fbo.width / 2, -fbo.height / 2) + p5.translate(-fbo.width / 2, -fbo.height / 2); p5.fill(0, 255, 255); p5.noStroke(); // Draw a shape that fills the new size @@ -641,14 +707,18 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', p5.plane(30, 30); await screenshot(); - }, + } ); visualTest( - "Framebuffer with depth disabled", + 'Framebuffer with depth disabled', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); - const fbo = p5.createFramebuffer({ width: 50, height: 50, depth: false }); + const fbo = p5.createFramebuffer({ + width: 50, + height: 50, + depth: false + }); fbo.draw(() => { p5.background(0, 0, 200); @@ -663,11 +733,11 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', p5.plane(50, 50); await screenshot(); - }, + } ); visualTest( - "Fixed-size framebuffer after manual resize", + 'Fixed-size framebuffer after manual resize', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Create fixed-size framebuffer @@ -687,7 +757,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', // Draw new content to the resized framebuffer fbo.draw(() => { p5.background(200, 255, 100); - p5.translate(-fbo.width / 2, -fbo.height / 2) + p5.translate(-fbo.width / 2, -fbo.height / 2); p5.fill(200, 0, 100); p5.noStroke(); // Draw content that uses the new size @@ -703,13 +773,13 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', p5.plane(35, 25); await screenshot(); - }, + } ); }); - visualSuite("Rendering attributes", function () { + visualSuite('Rendering attributes', function () { visualTest( - "noSmooth() does not crash and disables antialiasing", + 'noSmooth() does not crash and disables antialiasing', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); await p5.noSmooth(); @@ -718,56 +788,51 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', p5.noStroke(); p5.circle(0, 0, 30); await screenshot(); - }, + } ); }); - visualSuite("Clipping", function () { - visualTest( - "Basic clipping with circles", - async function (p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - p5.background("white"); + visualSuite('Clipping', function () { + visualTest('Basic clipping with circles', async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + p5.background('white'); - // Draw some circles that extend beyond the clipping area - p5.fill("red"); - p5.noStroke(); - p5.circle(-15, -15, 25); - p5.fill("green"); - p5.circle(15, -15, 25); - p5.fill("blue"); - p5.circle(-15, 15, 25); - p5.fill("yellow"); - p5.circle(15, 15, 25); - - // Apply clipping to a smaller rectangle in the center - p5.push(); - p5.clip(() => { - p5.rect(-12.5, -12.5, 25, 25); - }); + // Draw some circles that extend beyond the clipping area + p5.fill('red'); + p5.noStroke(); + p5.circle(-15, -15, 25); + p5.fill('green'); + p5.circle(15, -15, 25); + p5.fill('blue'); + p5.circle(-15, 15, 25); + p5.fill('yellow'); + p5.circle(15, 15, 25); + + // Apply clipping to a smaller rectangle in the center + p5.push(); + p5.clip(() => { + p5.rect(-12.5, -12.5, 25, 25); + }); - // Draw more circles that should be clipped to the rectangle - p5.fill("purple"); - p5.circle(-8, -8, 16); - p5.fill("orange"); - p5.circle(8, 8, 16); - p5.fill("cyan"); - p5.circle(0, 0, 12); + // Draw more circles that should be clipped to the rectangle + p5.fill('purple'); + p5.circle(-8, -8, 16); + p5.fill('orange'); + p5.circle(8, 8, 16); + p5.fill('cyan'); + p5.circle(0, 0, 12); - p5.pop(); + p5.pop(); - await screenshot(); - }, - ); + await screenshot(); + }); }); visualSuite('Typography', function () { visualSuite('textFont', function () { visualTest('with a font file in WebGPU', async function (p5, screenshot) { await p5.createCanvas(100, 100, p5.WEBGPU); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); + const font = await p5.loadFont('test/unit/assets/Inconsolata-Bold.ttf'); p5.textFont(font); p5.textAlign(p5.LEFT, p5.TOP); p5.textSize(35); @@ -777,100 +842,118 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', }); visualSuite('textWeight', function () { - visualTest('can control variable fonts from files in WebGPU', async function (p5, screenshot) { - await p5.createCanvas(100, 100, p5.WEBGPU); - const font = await p5.loadFont( - 'test/unit/assets/BricolageGrotesque-Variable.ttf' - ); - for (let weight = 400; weight <= 800; weight += 100) { - p5.push(); - p5.background(255); - p5.translate(-p5.width/2, -p5.height/2); - p5.textFont(font); - p5.textAlign(p5.LEFT, p5.TOP); - p5.textSize(35); - p5.textWeight(weight); - p5.text('p5*js', 0, 10, p5.width); - p5.pop(); - await screenshot(); - } - }); - }); - - visualSuite('textAlign', function () { - visualSuite('webgpu mode', () => { - visualTest('all alignments with single word', async function (p5, screenshot) { - const alignments = [ - { alignX: p5.LEFT, alignY: p5.TOP }, - { alignX: p5.CENTER, alignY: p5.TOP }, - { alignX: p5.RIGHT, alignY: p5.TOP }, - { alignX: p5.LEFT, alignY: p5.CENTER }, - { alignX: p5.CENTER, alignY: p5.CENTER }, - { alignX: p5.RIGHT, alignY: p5.CENTER }, - { alignX: p5.LEFT, alignY: p5.BOTTOM }, - { alignX: p5.CENTER, alignY: p5.BOTTOM }, - { alignX: p5.RIGHT, alignY: p5.BOTTOM } - ]; - - await p5.createCanvas(300, 300, p5.WEBGPU); - p5.translate(-p5.width/2, -p5.height/2); - p5.textSize(60); + visualTest( + 'can control variable fonts from files in WebGPU', + async function (p5, screenshot) { + await p5.createCanvas(100, 100, p5.WEBGPU); const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' + 'test/unit/assets/BricolageGrotesque-Variable.ttf' ); - p5.textFont(font); - for (const alignment of alignments) { - p5.background(255); - p5.textAlign(alignment.alignX, alignment.alignY); - const bb = p5.textBounds('Single Line', p5.width / 2, p5.height / 2); + for (let weight = 400; weight <= 800; weight += 100) { p5.push(); - p5.push() - p5.noFill(); - p5.stroke('red'); - p5.rect(bb.x, bb.y, bb.w, bb.h); - p5.pop() - p5.fill(0) - p5.text('Single Line', p5.width / 2, p5.height / 2); + p5.background(255); + p5.translate(-p5.width / 2, -p5.height / 2); + p5.textFont(font); + p5.textAlign(p5.LEFT, p5.TOP); + p5.textSize(35); + p5.textWeight(weight); + p5.text('p5*js', 0, 10, p5.width); p5.pop(); await screenshot(); } - }); + } + ); + }); - visualTest('all alignments with single line', async function (p5, screenshot) { - const alignments = [ - { alignX: p5.LEFT, alignY: p5.TOP }, - { alignX: p5.CENTER, alignY: p5.TOP }, - { alignX: p5.RIGHT, alignY: p5.TOP }, - { alignX: p5.LEFT, alignY: p5.CENTER }, - { alignX: p5.CENTER, alignY: p5.CENTER }, - { alignX: p5.RIGHT, alignY: p5.CENTER }, - { alignX: p5.LEFT, alignY: p5.BOTTOM }, - { alignX: p5.CENTER, alignY: p5.BOTTOM }, - { alignX: p5.RIGHT, alignY: p5.BOTTOM } - ]; - - await p5.createCanvas(300, 300, p5.WEBGPU); - p5.translate(-p5.width/2, -p5.height/2); - p5.textSize(45); - const font = await p5.loadFont( - 'test/unit/assets/Inconsolata-Bold.ttf' - ); - p5.textFont(font); - for (const alignment of alignments) { - p5.background(255); - p5.textAlign(alignment.alignX, alignment.alignY); - p5.text('Single Line', p5.width / 2, p5.height / 2); - const bb = p5.textBounds('Single Line', p5.width / 2, p5.height / 2); - p5.push(); - p5.noFill(); - p5.stroke('red'); - p5.rect(bb.x, bb.y, bb.w, bb.h); - p5.pop(); - await screenshot(); + visualSuite('textAlign', function () { + visualSuite('webgpu mode', () => { + visualTest( + 'all alignments with single word', + async function (p5, screenshot) { + const alignments = [ + { alignX: p5.LEFT, alignY: p5.TOP }, + { alignX: p5.CENTER, alignY: p5.TOP }, + { alignX: p5.RIGHT, alignY: p5.TOP }, + { alignX: p5.LEFT, alignY: p5.CENTER }, + { alignX: p5.CENTER, alignY: p5.CENTER }, + { alignX: p5.RIGHT, alignY: p5.CENTER }, + { alignX: p5.LEFT, alignY: p5.BOTTOM }, + { alignX: p5.CENTER, alignY: p5.BOTTOM }, + { alignX: p5.RIGHT, alignY: p5.BOTTOM } + ]; + + await p5.createCanvas(300, 300, p5.WEBGPU); + p5.translate(-p5.width / 2, -p5.height / 2); + p5.textSize(60); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' + ); + p5.textFont(font); + for (const alignment of alignments) { + p5.background(255); + p5.textAlign(alignment.alignX, alignment.alignY); + const bb = p5.textBounds( + 'Single Line', + p5.width / 2, + p5.height / 2 + ); + p5.push(); + p5.push(); + p5.noFill(); + p5.stroke('red'); + p5.rect(bb.x, bb.y, bb.w, bb.h); + p5.pop(); + p5.fill(0); + p5.text('Single Line', p5.width / 2, p5.height / 2); + p5.pop(); + await screenshot(); + } } - }); + ); + + visualTest( + 'all alignments with single line', + async function (p5, screenshot) { + const alignments = [ + { alignX: p5.LEFT, alignY: p5.TOP }, + { alignX: p5.CENTER, alignY: p5.TOP }, + { alignX: p5.RIGHT, alignY: p5.TOP }, + { alignX: p5.LEFT, alignY: p5.CENTER }, + { alignX: p5.CENTER, alignY: p5.CENTER }, + { alignX: p5.RIGHT, alignY: p5.CENTER }, + { alignX: p5.LEFT, alignY: p5.BOTTOM }, + { alignX: p5.CENTER, alignY: p5.BOTTOM }, + { alignX: p5.RIGHT, alignY: p5.BOTTOM } + ]; + + await p5.createCanvas(300, 300, p5.WEBGPU); + p5.translate(-p5.width / 2, -p5.height / 2); + p5.textSize(45); + const font = await p5.loadFont( + 'test/unit/assets/Inconsolata-Bold.ttf' + ); + p5.textFont(font); + for (const alignment of alignments) { + p5.background(255); + p5.textAlign(alignment.alignX, alignment.alignY); + p5.text('Single Line', p5.width / 2, p5.height / 2); + const bb = p5.textBounds( + 'Single Line', + p5.width / 2, + p5.height / 2 + ); + p5.push(); + p5.noFill(); + p5.stroke('red'); + p5.rect(bb.x, bb.y, bb.w, bb.h); + p5.pop(); + await screenshot(); + } + } + ); - visualTest('all alignments with multi-lines and wrap word', + visualTest( + 'all alignments with multi-lines and wrap word', async function (p5, screenshot) { const alignments = [ { alignX: p5.LEFT, alignY: p5.TOP }, @@ -885,7 +968,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', ]; await p5.createCanvas(150, 100, p5.WEBGPU); - p5.translate(-p5.width/2, -p5.height/2); + p5.translate(-p5.width / 2, -p5.height / 2); p5.textSize(20); p5.textWrap(p5.WORD); const font = await p5.loadFont( @@ -950,7 +1033,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', ]; await p5.createCanvas(150, 100, p5.WEBGPU); - p5.translate(-p5.width/2, -p5.height/2); + p5.translate(-p5.width / 2, -p5.height / 2); p5.textSize(19); p5.textWrap(p5.CHAR); const font = await p5.loadFont( @@ -1015,7 +1098,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', ]; await p5.createCanvas(150, 100, p5.WEBGPU); - p5.translate(-p5.width/2, -p5.height/2); + p5.translate(-p5.width / 2, -p5.height / 2); p5.textSize(20); const font = await p5.loadFont( @@ -1040,7 +1123,13 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', p5.fill(0); p5.noStroke(); - p5.text('Line 1\nLine 2\nLine 3', xPos, yPos, boxWidth, boxHeight); + p5.text( + 'Line 1\nLine 2\nLine 3', + xPos, + yPos, + boxWidth, + boxHeight + ); const bb = p5.textBounds( 'Line 1\nLine 2\nLine 3', xPos, @@ -1061,9 +1150,9 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', }); }); - visualSuite("Buffer Alignment", function () { + visualSuite('Buffer Alignment', function () { visualTest( - "buildGeometry with non-4-byte-aligned index buffer size", + 'buildGeometry with non-4-byte-aligned index buffer size', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); @@ -1082,9 +1171,12 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', for (let i = 0; i < 1323; i++) { p5.fill(p5.random(360), 80, 90); p5.triangle( - p5.random(-25, 25), p5.random(-25, 25), - p5.random(-25, 25), p5.random(-25, 25), - p5.random(-25, 25), p5.random(-25, 25) + p5.random(-25, 25), + p5.random(-25, 25), + p5.random(-25, 25), + p5.random(-25, 25), + p5.random(-25, 25), + p5.random(-25, 25) ); } }); @@ -1097,9 +1189,9 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', ); }); - visualSuite("Immediate Mode Buffer Reuse", function () { + visualSuite('Immediate Mode Buffer Reuse', function () { visualTest( - "beginShape/endShape reuses buffers across frames", + 'beginShape/endShape reuses buffers across frames', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); @@ -1108,9 +1200,9 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', // isn't FORCED to allocate new buffers, and should be trying // to reuse them. const positions = [ - { x: -15, y: -10 }, // Frame 1: left - { x: 0, y: -10 }, // Frame 2: center - { x: 15, y: -10 } // Frame 3: right + { x: -15, y: -10 }, // Frame 1: left + { x: 0, y: -10 }, // Frame 2: center + { x: 15, y: -10 } // Frame 3: right ]; for (let frame = 0; frame < 3; frame++) { @@ -1124,9 +1216,9 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', // same geometry every frame. We expect to see different results // if it's correctly updating the buffers. p5.beginShape(); - p5.vertex(pos.x - 5, pos.y + 10); // bottom-left - p5.vertex(pos.x + 5, pos.y + 10); // bottom-right - p5.vertex(pos.x, pos.y); // top + p5.vertex(pos.x - 5, pos.y + 10); // bottom-left + p5.vertex(pos.x + 5, pos.y + 10); // bottom-right + p5.vertex(pos.x, pos.y); // top p5.endShape(p5.CLOSE); await screenshot(); @@ -1135,7 +1227,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', ); }); - visualSuite("Image Based Lighting", function () { + visualSuite('Image Based Lighting', function () { const shinesses = [50, 150]; for (const shininess of shinesses) { visualTest( @@ -1174,7 +1266,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', } }); - visualSuite('transformation', function() { + visualSuite('transformation', function () { visualTest('outside of push() and pop()', async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); p5.background(200); @@ -1184,24 +1276,27 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', }); }); - visualSuite('Compute shaders', function() { + visualSuite('Compute shaders', function () { visualTest( 'Storage buffer (float array) can be read in a vertex shader for instanced rendering', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Positions for 3 spheres: (-15,0), (0,0), (15,0) const positions = p5.createStorage([-15, 0, 0, 0, 15, 0]); - const sphereShader = p5.baseMaterialShader().modify(() => { - const posData = p5.uniformStorage(); - p5.getWorldInputs((inputs) => { - const idx = p5.instanceIndex; - inputs.position.x += posData[idx * 2]; - inputs.position.y += posData[idx * 2 + 1]; - return inputs; - }); - }, { p5 }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const posData = p5.uniformStorage(); + p5.getWorldInputs(inputs => { + const idx = p5.instanceIndex; + inputs.position.x += posData[idx * 2]; + inputs.position.y += posData[idx * 2 + 1]; + return inputs; + }); + }, + { p5 } + ); sphereShader.setUniform('posData', positions); const geo = p5.buildGeometry(() => p5.sphere(5)); @@ -1217,28 +1312,34 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Compute shader writes float values to storage buffer, vertex shader reads them', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Start with zeros; compute shader will write [20, -10] const offset = p5.createStorage(2); - const computeShader = p5.buildComputeShader(() => { - const buf = p5.uniformStorage(); - buf[0] = 20; - buf[1] = -10; - }, { p5 }); + const computeShader = p5.buildComputeShader( + () => { + const buf = p5.uniformStorage(); + buf[0] = 20; + buf[1] = -10; + }, + { p5 } + ); computeShader.setUniform('buf', offset); p5.compute(computeShader, 1); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage(); - p5.getWorldInputs((inputs) => { - inputs.position.x += buf[0]; - inputs.position.y += buf[1]; - return inputs; - }); - }, { p5 }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage(); + p5.getWorldInputs(inputs => { + inputs.position.x += buf[0]; + inputs.position.y += buf[1]; + return inputs; + }); + }, + { p5 } + ); sphereShader.setUniform('buf', offset); const geo = p5.buildGeometry(() => p5.sphere(5)); @@ -1254,27 +1355,33 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Compute shader reads and transforms float array values', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Initialize with [10, 0] - compute will double x to get [20, 0] const buf = p5.createStorage([10, 0]); - const computeShader = p5.buildComputeShader(() => { - const data = p5.uniformStorage(); - data[0] = data[0] * 2; - }, { p5 }); + const computeShader = p5.buildComputeShader( + () => { + const data = p5.uniformStorage(); + data[0] = data[0] * 2; + }, + { p5 } + ); computeShader.setUniform('data', buf); p5.compute(computeShader, 1); - const sphereShader = p5.baseMaterialShader().modify(() => { - const data = p5.uniformStorage(); - p5.getWorldInputs((inputs) => { - inputs.position.x += data[0]; - inputs.position.y += data[1]; - return inputs; - }); - }, { p5 }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const data = p5.uniformStorage(); + p5.getWorldInputs(inputs => { + inputs.position.x += data[0]; + inputs.position.y += data[1]; + return inputs; + }); + }, + { p5 } + ); sphereShader.setUniform('data', buf); const geo = p5.buildGeometry(() => p5.sphere(5)); @@ -1288,28 +1395,30 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', } ); - visualTest( 'Struct storage buffer fields can be read in a vertex shader for instanced rendering', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Three particles at known positions: left, center, right const particles = p5.createStorage([ { position: [-15, 0] }, - { position: [0, 0] }, - { position: [15, 0] }, + { position: [0, 0] }, + { position: [15, 0] } ]); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage('buf', particles); - p5.getWorldInputs((inputs) => { - const p = buf[p5.instanceIndex].position; - inputs.position.x += p.x; - inputs.position.y += p.y; - return inputs; - }); - }, { p5, particles }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage('buf', particles); + p5.getWorldInputs(inputs => { + const p = buf[p5.instanceIndex].position; + inputs.position.x += p.x; + inputs.position.y += p.y; + return inputs; + }); + }, + { p5, particles } + ); const geo = p5.buildGeometry(() => p5.sphere(5)); p5.background(200); @@ -1324,25 +1433,28 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Struct storage buffer fields can use p5.Vector values', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Three particles at known positions: left, center, right const particles = p5.createStorage([ { position: p5.createVector(-15, 0) }, - { position: p5.createVector(0, 0) }, - { position: p5.createVector(15, 0) }, + { position: p5.createVector(0, 0) }, + { position: p5.createVector(15, 0) } ]); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage('buf', particles); - p5.getWorldInputs((inputs) => { - const p = buf[p5.instanceIndex].position; - inputs.position.x += p.x; - inputs.position.y += p.y; - return inputs; - }); - }, { p5, particles }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage('buf', particles); + p5.getWorldInputs(inputs => { + const p = buf[p5.instanceIndex].position; + inputs.position.x += p.x; + inputs.position.y += p.y; + return inputs; + }); + }, + { p5, particles } + ); const geo = p5.buildGeometry(() => p5.sphere(5)); p5.background(200); @@ -1357,25 +1469,28 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Struct storage buffer fields can be read using an inline schema template', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Same layout as above but schema is declared inline rather than via the buffer const particles = p5.createStorage([ { position: [-15, 0] }, - { position: [0, 0] }, - { position: [15, 0] }, + { position: [0, 0] }, + { position: [15, 0] } ]); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage('buf', { position: [0, 0] }); - p5.getWorldInputs((inputs) => { - const p = buf[p5.instanceIndex].position; - inputs.position.x += p.x; - inputs.position.y += p.y; - return inputs; - }); - }, { p5 }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage('buf', { position: [0, 0] }); + p5.getWorldInputs(inputs => { + const p = buf[p5.instanceIndex].position; + inputs.position.x += p.x; + inputs.position.y += p.y; + return inputs; + }); + }, + { p5 } + ); sphereShader.setUniform('buf', particles); const geo = p5.buildGeometry(() => p5.sphere(5)); @@ -1391,28 +1506,32 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Compute shader writes to struct storage fields, vertex shader reads them', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); - const particles = p5.createStorage([ - { position: [0, 0] }, - ]); + const particles = p5.createStorage([{ position: [0, 0] }]); - const computeShader = p5.buildComputeShader(() => { - const buf = p5.uniformStorage('buf', particles); - buf[p5.index.x].position = [15, -10]; - }, { p5, particles }); + const computeShader = p5.buildComputeShader( + () => { + const buf = p5.uniformStorage('buf', particles); + buf[p5.index.x].position = [15, -10]; + }, + { p5, particles } + ); p5.compute(computeShader, 1); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage('buf', particles); - p5.getWorldInputs((inputs) => { - const pos = buf[0].position; - inputs.position.x += pos.x; - inputs.position.y += pos.y; - return inputs; - }); - }, { p5, particles }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage('buf', particles); + p5.getWorldInputs(inputs => { + const pos = buf[0].position; + inputs.position.x += pos.x; + inputs.position.y += pos.y; + return inputs; + }); + }, + { p5, particles } + ); const geo = p5.buildGeometry(() => p5.sphere(5)); p5.background(200); @@ -1427,29 +1546,35 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Compute shader reads and updates struct fields (position += velocity)', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const particles = p5.createStorage([ - { position: [0, 0], velocity: [15, -10] }, + { position: [0, 0], velocity: [15, -10] } ]); - const computeShader = p5.buildComputeShader(() => { - const buf = p5.uniformStorage('buf', particles); - const idx = p5.index.x; - buf[idx].position = buf[idx].position + buf[idx].velocity; - }, { p5, particles }); + const computeShader = p5.buildComputeShader( + () => { + const buf = p5.uniformStorage('buf', particles); + const idx = p5.index.x; + buf[idx].position = buf[idx].position + buf[idx].velocity; + }, + { p5, particles } + ); p5.compute(computeShader, 1); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage('buf', particles); - p5.getWorldInputs((inputs) => { - const pos = buf[0].position; - inputs.position.x += pos.x; - inputs.position.y += pos.y; - return inputs; - }); - }, { p5, particles }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage('buf', particles); + p5.getWorldInputs(inputs => { + const pos = buf[0].position; + inputs.position.x += pos.x; + inputs.position.y += pos.y; + return inputs; + }); + }, + { p5, particles } + ); const geo = p5.buildGeometry(() => p5.sphere(5)); p5.background(200); @@ -1464,31 +1589,37 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Compute shader updates struct fields via intermediate struct variable', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const particles = p5.createStorage([ - { position: [0, 0], velocity: [15, -10] }, + { position: [0, 0], velocity: [15, -10] } ]); // Store the struct element proxy in a variable and assign through it - const computeShader = p5.buildComputeShader(() => { - const buf = p5.uniformStorage('buf', particles); - const idx = p5.index.x; - const entry = buf[idx]; - entry.position = entry.position + entry.velocity; - }, { p5, particles }); + const computeShader = p5.buildComputeShader( + () => { + const buf = p5.uniformStorage('buf', particles); + const idx = p5.index.x; + const entry = buf[idx]; + entry.position = entry.position + entry.velocity; + }, + { p5, particles } + ); p5.compute(computeShader, 1); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage('buf', particles); - p5.getWorldInputs((inputs) => { - const pos = buf[0].position; - inputs.position.x += pos.x; - inputs.position.y += pos.y; - return inputs; - }); - }, { p5, particles }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage('buf', particles); + p5.getWorldInputs(inputs => { + const pos = buf[0].position; + inputs.position.x += pos.x; + inputs.position.y += pos.y; + return inputs; + }); + }, + { p5, particles } + ); const geo = p5.buildGeometry(() => p5.sphere(5)); p5.background(200); @@ -1503,32 +1634,38 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Compute shader updates struct fields via intermediate field variable', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const particles = p5.createStorage([ - { position: [0, 0], velocity: [15, -10] }, + { position: [0, 0], velocity: [15, -10] } ]); // Store a field value in an intermediate variable, update it, write it back - const computeShader = p5.buildComputeShader(() => { - const buf = p5.uniformStorage('buf', particles); - const idx = p5.index.x; - let pos = buf[idx].position; - pos = pos + buf[idx].velocity; - buf[idx].position = pos; - }, { p5, particles }); + const computeShader = p5.buildComputeShader( + () => { + const buf = p5.uniformStorage('buf', particles); + const idx = p5.index.x; + let pos = buf[idx].position; + pos = pos + buf[idx].velocity; + buf[idx].position = pos; + }, + { p5, particles } + ); p5.compute(computeShader, 1); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage('buf', particles); - p5.getWorldInputs((inputs) => { - const pos = buf[0].position; - inputs.position.x += pos.x; - inputs.position.y += pos.y; - return inputs; - }); - }, { p5, particles }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage('buf', particles); + p5.getWorldInputs(inputs => { + const pos = buf[0].position; + inputs.position.x += pos.x; + inputs.position.y += pos.y; + return inputs; + }); + }, + { p5, particles } + ); const geo = p5.buildGeometry(() => p5.sphere(5)); p5.background(200); @@ -1543,32 +1680,38 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Compute shader writes a whole struct element as an object literal', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const particles = p5.createStorage([ - { position: [0, 0], velocity: [15, -10] }, + { position: [0, 0], velocity: [15, -10] } ]); - const computeShader = p5.buildComputeShader(() => { - const buf = p5.uniformStorage('buf', particles); - const idx = p5.index.x; - let pos = buf[idx].position; - let vel = buf[idx].velocity; - pos = pos + vel; - buf[idx] = { position: pos, velocity: vel }; - }, { p5, particles }); + const computeShader = p5.buildComputeShader( + () => { + const buf = p5.uniformStorage('buf', particles); + const idx = p5.index.x; + let pos = buf[idx].position; + let vel = buf[idx].velocity; + pos = pos + vel; + buf[idx] = { position: pos, velocity: vel }; + }, + { p5, particles } + ); p5.compute(computeShader, 1); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage('buf', particles); - p5.getWorldInputs((inputs) => { - const pos = buf[0].position; - inputs.position.x += pos.x; - inputs.position.y += pos.y; - return inputs; - }); - }, { p5, particles }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage('buf', particles); + p5.getWorldInputs(inputs => { + const pos = buf[0].position; + inputs.position.x += pos.x; + inputs.position.y += pos.y; + return inputs; + }); + }, + { p5, particles } + ); const geo = p5.buildGeometry(() => p5.sphere(5)); p5.background(200); @@ -1583,30 +1726,34 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Compute shader assigns to a swizzle of a struct vector field', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); - const particles = p5.createStorage([ - { position: [15, 10] }, - ]); + const particles = p5.createStorage([{ position: [15, 10] }]); // Negate position.y via swizzle assignment - const computeShader = p5.buildComputeShader(() => { - const buf = p5.uniformStorage('buf', particles); - const idx = p5.index.x; - buf[idx].position.y *= -1; - }, { p5, particles }); + const computeShader = p5.buildComputeShader( + () => { + const buf = p5.uniformStorage('buf', particles); + const idx = p5.index.x; + buf[idx].position.y *= -1; + }, + { p5, particles } + ); p5.compute(computeShader, 1); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage('buf', particles); - p5.getWorldInputs((inputs) => { - const pos = buf[0].position; - inputs.position.x += pos.x; - inputs.position.y += pos.y; - return inputs; - }); - }, { p5, particles }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage('buf', particles); + p5.getWorldInputs(inputs => { + const pos = buf[0].position; + inputs.position.x += pos.x; + inputs.position.y += pos.y; + return inputs; + }); + }, + { p5, particles } + ); const geo = p5.buildGeometry(() => p5.sphere(5)); p5.background(200); @@ -1621,36 +1768,42 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', visualTest( 'Compute shader assigns to a swizzle of a struct vector field inside an if statement', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); const particles = p5.createStorage([ - { position: [0, 0], velocity: [5, 5] }, + { position: [0, 0], velocity: [5, 5] } ]); // Move by velocity, then negate velocity.y if position.y > 0. // After 1st run: position=[5,5], velocity=[5,-5]. // After 2nd run: position=[10,0], velocity=[5,-5]. - const computeShader = p5.buildComputeShader(() => { - const buf = p5.uniformStorage('buf', particles); - const idx = p5.index.x; - buf[idx].position += buf[idx].velocity; - if (buf[idx].position.y > 0) { - buf[idx].velocity.y *= -1; - } - }, { p5, particles }); + const computeShader = p5.buildComputeShader( + () => { + const buf = p5.uniformStorage('buf', particles); + const idx = p5.index.x; + buf[idx].position += buf[idx].velocity; + if (buf[idx].position.y > 0) { + buf[idx].velocity.y *= -1; + } + }, + { p5, particles } + ); p5.compute(computeShader, 1); p5.compute(computeShader, 1); - const sphereShader = p5.baseMaterialShader().modify(() => { - const buf = p5.uniformStorage('buf', particles); - p5.getWorldInputs((inputs) => { - const pos = buf[0].position; - inputs.position.x += pos.x; - inputs.position.y += pos.y; - return inputs; - }); - }, { p5, particles }); + const sphereShader = p5.baseMaterialShader().modify( + () => { + const buf = p5.uniformStorage('buf', particles); + p5.getWorldInputs(inputs => { + const pos = buf[0].position; + inputs.position.x += pos.x; + inputs.position.y += pos.y; + return inputs; + }); + }, + { p5, particles } + ); const geo = p5.buildGeometry(() => p5.sphere(5)); p5.background(200); @@ -1664,28 +1817,12 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', ); }); - visualSuite('2D Shapes', function() { - visualTest('TRIANGLE_FAN with per-vertex fills', async function(p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - p5.background(255); - p5.beginShape(p5.TRIANGLE_FAN); - p5.fill('red'); - p5.vertex(0, 0); - const n = 10; - const r = 20; - p5.fill('blue'); - for (let i = 0; i <= n; i++) { - const angle = i/n * p5.TWO_PI; - p5.vertex(r*p5.cos(angle), r*p5.sin(angle)); - } - p5.endShape(); - await screenshot(); - }); - - visualTest('TRIANGLE_FAN in p5.Geometry with per-vertex fills', async function(p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - p5.background(255); - const geom = p5.buildGeometry(() => { + visualSuite('2D Shapes', function () { + visualTest( + 'TRIANGLE_FAN with per-vertex fills', + async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + p5.background(255); p5.beginShape(p5.TRIANGLE_FAN); p5.fill('red'); p5.vertex(0, 0); @@ -1693,32 +1830,42 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', const r = 20; p5.fill('blue'); for (let i = 0; i <= n; i++) { - const angle = i/n * p5.TWO_PI; - p5.vertex(r*p5.cos(angle), r*p5.sin(angle)); + const angle = (i / n) * p5.TWO_PI; + p5.vertex(r * p5.cos(angle), r * p5.sin(angle)); } p5.endShape(); - }); - p5.model(geom); - await screenshot(); - }); + await screenshot(); + } + ); - visualTest('TRIANGLE_STRIP with per-vertex fills', async function(p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - p5.background(255); - p5.beginShape(p5.TRIANGLE_STRIP); - const n = 6; - for (let i = 0; i < n; i++) { - p5.fill(i % 2 === 0 ? 'red' : 'blue'); - p5.vertex(p5.map(i, 0, n - 1, -20, 20), i % 2 === 0 ? -10 : 10); + visualTest( + 'TRIANGLE_FAN in p5.Geometry with per-vertex fills', + async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + p5.background(255); + const geom = p5.buildGeometry(() => { + p5.beginShape(p5.TRIANGLE_FAN); + p5.fill('red'); + p5.vertex(0, 0); + const n = 10; + const r = 20; + p5.fill('blue'); + for (let i = 0; i <= n; i++) { + const angle = (i / n) * p5.TWO_PI; + p5.vertex(r * p5.cos(angle), r * p5.sin(angle)); + } + p5.endShape(); + }); + p5.model(geom); + await screenshot(); } - p5.endShape(); - await screenshot(); - }); + ); - visualTest('TRIANGLE_STRIP in p5.Geometry with per-vertex fills', async function(p5, screenshot) { - await p5.createCanvas(50, 50, p5.WEBGPU); - p5.background(255); - const geom = p5.buildGeometry(() => { + visualTest( + 'TRIANGLE_STRIP with per-vertex fills', + async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + p5.background(255); p5.beginShape(p5.TRIANGLE_STRIP); const n = 6; for (let i = 0; i < n; i++) { @@ -1726,16 +1873,34 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', p5.vertex(p5.map(i, 0, n - 1, -20, 20), i % 2 === 0 ? -10 : 10); } p5.endShape(); - }); - p5.model(geom); - await screenshot(); - }); + await screenshot(); + } + ); + + visualTest( + 'TRIANGLE_STRIP in p5.Geometry with per-vertex fills', + async function (p5, screenshot) { + await p5.createCanvas(50, 50, p5.WEBGPU); + p5.background(255); + const geom = p5.buildGeometry(() => { + p5.beginShape(p5.TRIANGLE_STRIP); + const n = 6; + for (let i = 0; i < n; i++) { + p5.fill(i % 2 === 0 ? 'red' : 'blue'); + p5.vertex(p5.map(i, 0, n - 1, -20, 20), i % 2 === 0 ? -10 : 10); + } + p5.endShape(); + }); + p5.model(geom); + await screenshot(); + } + ); }); - visualSuite('Feedback', function() { + visualSuite('Feedback', function () { visualTest( 'Drawing accumulates across frames when background is set in setup', - async function(p5, screenshot) { + async function (p5, screenshot) { await p5.createCanvas(50, 50, p5.WEBGPU); // Set an initial background before the draw loop starts. @@ -1744,7 +1909,7 @@ visualTest('randomGaussian() in a fragment loop averages to the mean (WebGPU)', return new Promise(resolve => { let frame = 0; - p5.draw = function() { + p5.draw = function () { // Draw circles without clearing, so they accumulate p5.noStroke(); p5.fill('blue'); diff --git a/test/unit/visual/visualTest.js b/test/unit/visual/visualTest.js index b761665d9c..31181d8217 100644 --- a/test/unit/visual/visualTest.js +++ b/test/unit/visual/visualTest.js @@ -129,7 +129,7 @@ export function visualSuite( */ export function checkMatch(actual, expected, p5) { - let scale = Math.min(MAX_SIDE/expected.width, MAX_SIDE/expected.height); + let scale = Math.min(MAX_SIDE / expected.width, MAX_SIDE / expected.height); const ratio = expected.width / expected.height; const narrow = ratio !== 1; if (narrow) { @@ -137,10 +137,7 @@ export function checkMatch(actual, expected, p5) { } for (const img of [actual, expected]) { - img.resize( - Math.ceil(img.width * scale), - Math.ceil(img.height * scale) - ); + img.resize(Math.ceil(img.width * scale), Math.ceil(img.height * scale)); } // Ensure both images have the same dimensions @@ -208,8 +205,10 @@ export function checkMatch(actual, expected, p5) { // Find the connected cluster size using BFS const clusterSize = findClusterSize( diffCanvas.pixels, - x, y, - width, height, + x, + y, + width, + height, 1, visited ); @@ -219,16 +218,19 @@ export function checkMatch(actual, expected, p5) { } // Define significance thresholds - const MIN_CLUSTER_SIZE = 4; // Minimum pixels in a significant cluster - const MAX_TOTAL_DIFF_PIXELS = 40; // Maximum total different pixels + const MIN_CLUSTER_SIZE = 4; // Minimum pixels in a significant cluster + const MAX_TOTAL_DIFF_PIXELS = 40; // Maximum total different pixels // Determine if the differences are significant - const nonLineShiftClusters = clusterSizes - .filter(c => !c.isLineShift && c.size >= MIN_CLUSTER_SIZE); + const nonLineShiftClusters = clusterSizes.filter( + c => !c.isLineShift && c.size >= MIN_CLUSTER_SIZE + ); // Calculate significant differences excluding line shifts - const significantDiffPixels = nonLineShiftClusters - .reduce((sum, c) => sum + c.size, 0); + const significantDiffPixels = nonLineShiftClusters.reduce( + (sum, c) => sum + c.size, + 0 + ); // Update the diff canvas diffCanvas.updatePixels(); @@ -238,16 +240,11 @@ export function checkMatch(actual, expected, p5) { expectedCanvas.remove(); // Determine test result - const ok = ( + const ok = diffCount === 0 || - ( - significantDiffPixels === 0 || - ( - (significantDiffPixels <= MAX_TOTAL_DIFF_PIXELS) && - (nonLineShiftClusters.length <= 2) // Not too many significant clusters - ) - ) - ); + significantDiffPixels === 0 || + (significantDiffPixels <= MAX_TOTAL_DIFF_PIXELS && + nonLineShiftClusters.length <= 2); // Not too many significant clusters return { ok, @@ -265,8 +262,10 @@ export function checkMatch(actual, expected, p5) { */ function findClusterSize( pixels, - startX, startY, - width, height, + startX, + startY, + width, + height, radius, visited ) { @@ -282,11 +281,8 @@ function findClusterSize( if (visited.has(pos)) continue; // Skip if not a diff pixel - if ( - pixels[pos] !== 255 || - pixels[pos + 1] !== 0 || - pixels[pos + 2] !== 0 - ) continue; + if (pixels[pos] !== 255 || pixels[pos + 1] !== 0 || pixels[pos + 2] !== 0) + continue; // Mark as visited visited.add(pos); @@ -390,98 +386,104 @@ export function visualTest( suiteFn = suiteFn.skip; } - suiteFn(testName, function() { + suiteFn(testName, function () { let name; let myp5; let lastDeviceRatio = window.devicePixelRatio; - beforeAll(function() { + beforeAll(function () { name = namePrefix + escapeName(testName); // Force everything to be 1x window.devicePixelRatio = 1; return new Promise(res => { - myp5 = new p5(function(p) { - p.setup = function() { + myp5 = new p5(function (p) { + p.setup = function () { res(); }; }); }); }); - afterAll(function() { + afterAll(function () { window.devicePixelRatio = lastDeviceRatio; myp5.remove(); }); - test('matches expected screenshots', async function() { - let expectedScreenshots; - try { - const metadata = JSON.parse(await readFile( - `./test/unit/visual/screenshots/${name}/metadata.json` - )); - expectedScreenshots = metadata.numScreenshots; - } catch (e) { - console.log(e); - expectedScreenshots = 0; - } + test( + 'matches expected screenshots', + async function () { + let expectedScreenshots; + try { + const metadata = JSON.parse( + await readFile( + `./test/unit/visual/screenshots/${name}/metadata.json` + ) + ); + expectedScreenshots = metadata.numScreenshots; + } catch (e) { + console.log(e); + expectedScreenshots = 0; + } - const actual = []; + const actual = []; - // Generate screenshots - await callback(myp5, async () => { - const img = await myp5.get(); - img.pixelDensity(1); - actual.push(img); - }); + // Generate screenshots + await callback(myp5, async () => { + const img = await myp5.get(); + img.pixelDensity(1); + actual.push(img); + }); + if (actual.length === 0) { + throw new Error( + 'No screenshots were generated. Check if your test generates screenshots correctly. If the test includes asynchronous operations, ensure they complete before the test ends.' + ); + } + if (expectedScreenshots && actual.length !== expectedScreenshots) { + throw new Error( + `Expected ${expectedScreenshots} screenshot(s) but generated ${actual.length}` + ); + } + if (!expectedScreenshots) { + await writeFile( + `./test/unit/visual/screenshots/${name}/metadata.json`, + JSON.stringify({ numScreenshots: actual.length }, null, 2) + ); + } - if (actual.length === 0) { - throw new Error('No screenshots were generated. Check if your test generates screenshots correctly. If the test includes asynchronous operations, ensure they complete before the test ends.'); - } - if (expectedScreenshots && actual.length !== expectedScreenshots) { - throw new Error( - `Expected ${expectedScreenshots} screenshot(s) but generated ${actual.length}` - ); - } - if (!expectedScreenshots) { - await writeFile( - `./test/unit/visual/screenshots/${name}/metadata.json`, - JSON.stringify({ numScreenshots: actual.length }, null, 2) + const expectedFilenames = actual.map( + (_, i) => + `./test/unit/visual/screenshots/${name}/${i.toString().padStart(3, '0')}.png` ); - } - - const expectedFilenames = actual.map( - (_, i) => `./test/unit/visual/screenshots/${name}/${i.toString().padStart(3, '0')}.png` - ); - const expected = expectedScreenshots - ? ( - await Promise.all( - expectedFilenames.map(path => myp5.loadImage(path.slice(2))) - ) - ) - : []; - - for (let i = 0; i < actual.length; i++) { - const flatName = name.replace(/\//g, '-'); - const actualFilename = `./test/unit/visual/actual-screenshots/${flatName}-${i.toString().padStart(3, '0')}.png`; - if (expected[i]) { - const result = checkMatch(actual[i], expected[i], myp5); - // Always save the actual image before potentially throwing an error - writeImageFile(actualFilename, toBase64(actual[i])); - if (!result.ok) { - const diffFilename = `./test/unit/visual/actual-screenshots/${flatName}-${i.toString().padStart(3, '0')}-diff.png`; - writeImageFile(diffFilename, toBase64(result.diff)); - throw new Error( - `Screenshots do not match! Expected:\n${toBase64(expected[i])}\n\nReceived:\n${toBase64(actual[i])}\n\nDiff:\n${toBase64(result.diff)}\n\n` + - 'If this is unexpected, paste these URLs into your browser to inspect them.\n\n' + - `If this change is expected, please delete the screenshots/${name} folder and run tests again to generate a new screenshot.` - ); + const expected = expectedScreenshots + ? await Promise.all( + expectedFilenames.map(path => myp5.loadImage(path.slice(2))) + ) + : []; + + for (let i = 0; i < actual.length; i++) { + const flatName = name.replace(/\//g, '-'); + const actualFilename = `./test/unit/visual/actual-screenshots/${flatName}-${i.toString().padStart(3, '0')}.png`; + if (expected[i]) { + const result = checkMatch(actual[i], expected[i], myp5); + // Always save the actual image before potentially throwing an error + writeImageFile(actualFilename, toBase64(actual[i])); + if (!result.ok) { + const diffFilename = `./test/unit/visual/actual-screenshots/${flatName}-${i.toString().padStart(3, '0')}-diff.png`; + writeImageFile(diffFilename, toBase64(result.diff)); + throw new Error( + `Screenshots do not match! Expected:\n${toBase64(expected[i])}\n\nReceived:\n${toBase64(actual[i])}\n\nDiff:\n${toBase64(result.diff)}\n\n` + + 'If this is unexpected, paste these URLs into your browser to inspect them.\n\n' + + `If this change is expected, please delete the screenshots/${name} folder and run tests again to generate a new screenshot.` + ); + } + } else { + writeImageFile(expectedFilenames[i], toBase64(actual[i])); + writeImageFile(actualFilename, toBase64(actual[i])); } - } else { - writeImageFile(expectedFilenames[i], toBase64(actual[i])); - writeImageFile(actualFilename, toBase64(actual[i])); } - } - }, timeout); + }, + timeout + ); }); } diff --git a/test/unit/webgl/3d_primitives.js b/test/unit/webgl/3d_primitives.js index 849c0db326..757b2e7bb7 100644 --- a/test/unit/webgl/3d_primitives.js +++ b/test/unit/webgl/3d_primitives.js @@ -1,64 +1,64 @@ import p5 from '../../../src/app.js'; -suite('3D Primitives', function() { +suite('3D Primitives', function () { var myp5; - beforeAll(function() { - myp5 = new p5(function(p) { - p.setup = function() { + beforeAll(function () { + myp5 = new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100, p.WEBGL); }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('p5.prototype.plane', function() { - test('should be a function', function() { + suite('p5.prototype.plane', function () { + test('should be a function', function () { assert.ok(myp5.plane); assert.typeOf(myp5.plane, 'function'); }); }); - suite('p5.prototype.box', function() { - test('should be a function', function() { + suite('p5.prototype.box', function () { + test('should be a function', function () { assert.ok(myp5.box); assert.typeOf(myp5.box, 'function'); }); }); - suite('p5.prototype.sphere', function() { - test('should be a function', function() { + suite('p5.prototype.sphere', function () { + test('should be a function', function () { assert.ok(myp5.sphere); assert.typeOf(myp5.sphere, 'function'); }); }); - suite('p5.prototype.cylinder', function() { - test('should be a function', function() { + suite('p5.prototype.cylinder', function () { + test('should be a function', function () { assert.ok(myp5.cylinder); assert.typeOf(myp5.cylinder, 'function'); }); }); - suite('p5.prototype.cone', function() { - test('should be a function', function() { + suite('p5.prototype.cone', function () { + test('should be a function', function () { assert.ok(myp5.cone); assert.typeOf(myp5.cone, 'function'); }); }); - suite('p5.prototype.ellipsoid', function() { - test('should be a function', function() { + suite('p5.prototype.ellipsoid', function () { + test('should be a function', function () { assert.ok(myp5.ellipsoid); assert.typeOf(myp5.ellipsoid, 'function'); }); }); - suite('p5.prototype.torus', function() { - test('should be a function', function() { + suite('p5.prototype.torus', function () { + test('should be a function', function () { assert.ok(myp5.torus); assert.typeOf(myp5.torus, 'function'); }); diff --git a/test/unit/webgl/interaction.js b/test/unit/webgl/interaction.js index f03433d841..4fe6ba4466 100644 --- a/test/unit/webgl/interaction.js +++ b/test/unit/webgl/interaction.js @@ -1,36 +1,36 @@ import p5 from '../../../src/app.js'; -suite('Interaction', function() { +suite('Interaction', function () { var myp5; - beforeAll(function() { - myp5 = new p5(function(p) { - p.setup = function() { + beforeAll(function () { + myp5 = new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100, p.WEBGL); }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('p5.prototype.orbitControl', function() { - test('should be a function', function() { + suite('p5.prototype.orbitControl', function () { + test('should be a function', function () { assert.ok(myp5.orbitControl); assert.typeOf(myp5.orbitControl, 'function'); }); }); - suite('p5.prototype.debugMode', function() { - test('should be a function', function() { + suite('p5.prototype.debugMode', function () { + test('should be a function', function () { assert.ok(myp5.debugMode); assert.typeOf(myp5.debugMode, 'function'); }); }); - suite('p5.prototype.noDebugMode', function() { - test('should be a function', function() { + suite('p5.prototype.noDebugMode', function () { + test('should be a function', function () { assert.ok(myp5.noDebugMode); assert.typeOf(myp5.noDebugMode, 'function'); }); diff --git a/test/unit/webgl/light.js b/test/unit/webgl/light.js index 15fad185e1..7d60804c46 100644 --- a/test/unit/webgl/light.js +++ b/test/unit/webgl/light.js @@ -1,22 +1,22 @@ import p5 from '../../../src/app.js'; -suite('light', function() { +suite('light', function () { var myp5; - beforeAll(function() { - myp5 = new p5(function(p) { - p.setup = function() { + beforeAll(function () { + myp5 = new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100, p.WEBGL); }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('Light', function() { - test('lightFalloff is initialised and set properly', function() { + suite('Light', function () { + test('lightFalloff is initialised and set properly', function () { assert.deepEqual(myp5._renderer.states.constantAttenuation, 1); assert.deepEqual(myp5._renderer.states.linearAttenuation, 0); assert.deepEqual(myp5._renderer.states.quadraticAttenuation, 0); @@ -26,7 +26,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.quadraticAttenuation, 4); }); - test('specularColor is initialised and set properly', function() { + test('specularColor is initialised and set properly', function () { assert.deepEqual(myp5._renderer.states.specularColors, [1, 1, 1]); assert.deepEqual(myp5._renderer.states.pointLightSpecularColors, []); assert.deepEqual( @@ -41,13 +41,12 @@ suite('light', function() { myp5._renderer.states.pointLightSpecularColors, [1, 0, 0] ); - assert.deepEqual(myp5._renderer.states.directionalLightSpecularColors, [ - 1, - 0, - 0 - ]); + assert.deepEqual( + myp5._renderer.states.directionalLightSpecularColors, + [1, 0, 0] + ); }); - test('noLights works', function() { + test('noLights works', function () { myp5.ambientLight(200, 0, 0); myp5.pointLight(255, 0, 0, 0, 0, 0); myp5.directionalLight(255, 0, 0, 0, 0, 0); @@ -75,14 +74,14 @@ suite('light', function() { }); }); - suite('spotlight inputs', function() { + suite('spotlight inputs', function () { beforeEach(() => myp5.noLights()); let angle = Math.PI / 4; let defaultAngle = Math.cos(Math.PI / 3); let cosAngle = Math.cos(angle); let conc = 7; let defaultConc = 100; - test('default', function() { + test('default', function () { assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, []); assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, []); assert.deepEqual(myp5._renderer.states.spotLightPositions, []); @@ -90,7 +89,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, []); assert.deepEqual(myp5._renderer.states.spotLightConc, []); }); - test('color,positions,directions', function() { + test('color,positions,directions', function () { let color = myp5.color(255, 0, 255); let positions = new p5.Vector(1, 2, 3); let directions = new p5.Vector(0, 1, 0); @@ -105,7 +104,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('color,positions,directions,angle', function() { + test('color,positions,directions,angle', function () { let color = myp5.color(255, 0, 255); let positions = new p5.Vector(1, 2, 3); let directions = new p5.Vector(0, 1, 0); @@ -120,7 +119,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('color,positions,directions,angle,conc', function() { + test('color,positions,directions,angle,conc', function () { let color = myp5.color(255, 0, 255); let positions = new p5.Vector(1, 2, 3); let directions = new p5.Vector(0, 1, 0); @@ -135,7 +134,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [conc]); }); - test('c1,c2,c3,positions,directions', function() { + test('c1,c2,c3,positions,directions', function () { let positions = new p5.Vector(1, 2, 3); let directions = new p5.Vector(0, 1, 0); myp5.spotLight(255, 0, 255, positions, directions); @@ -149,7 +148,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('color,p1,p2,p3,directions', function() { + test('color,p1,p2,p3,directions', function () { let color = myp5.color(255, 0, 255); let directions = new p5.Vector(0, 1, 0); myp5.spotLight(color, 1, 2, 3, directions); @@ -163,7 +162,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('color,positions,r1,r2,r3', function() { + test('color,positions,r1,r2,r3', function () { let color = myp5.color(255, 0, 255); let positions = new p5.Vector(1, 2, 3); myp5.spotLight(color, positions, 0, 1, 0); @@ -177,7 +176,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('c1,c2,c3,positions,directions,angle', function() { + test('c1,c2,c3,positions,directions,angle', function () { let positions = new p5.Vector(1, 2, 3); let directions = new p5.Vector(0, 1, 0); myp5.spotLight(255, 0, 255, positions, directions, angle); @@ -191,7 +190,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('color,p1,p2,p3,directions,angle', function() { + test('color,p1,p2,p3,directions,angle', function () { let color = myp5.color(255, 0, 255); let directions = new p5.Vector(0, 1, 0); myp5.spotLight(color, 1, 2, 3, directions, angle); @@ -205,7 +204,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('color,positions,r1,r2,r3,angle', function() { + test('color,positions,r1,r2,r3,angle', function () { let color = myp5.color(255, 0, 255); let positions = new p5.Vector(1, 2, 3); myp5.spotLight(color, positions, 0, 1, 0, angle); @@ -219,7 +218,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('c1,c2,c3,positions,directions,angle,conc', function() { + test('c1,c2,c3,positions,directions,angle,conc', function () { let positions = new p5.Vector(1, 2, 3); let directions = new p5.Vector(0, 1, 0); myp5.spotLight(255, 0, 255, positions, directions, angle, conc); @@ -233,7 +232,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [7]); }); - test('color,p1,p2,p3,directions,angle,conc', function() { + test('color,p1,p2,p3,directions,angle,conc', function () { let color = myp5.color(255, 0, 255); let directions = new p5.Vector(0, 1, 0); myp5.spotLight(color, 1, 2, 3, directions, angle, conc); @@ -247,7 +246,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [7]); }); - test('color,positions,r1,r2,r3,angle,conc', function() { + test('color,positions,r1,r2,r3,angle,conc', function () { let color = myp5.color(255, 0, 255); let positions = new p5.Vector(1, 2, 3); myp5.spotLight(color, positions, 0, 1, 0, angle, conc); @@ -261,7 +260,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [7]); }); - test('c1,c2,c3,p1,p2,p3,directions', function() { + test('c1,c2,c3,p1,p2,p3,directions', function () { let directions = new p5.Vector(0, 1, 0); myp5.spotLight(255, 0, 255, 1, 2, 3, directions); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); @@ -274,7 +273,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('c1,c2,c3,positions,r1,r2,r3', function() { + test('c1,c2,c3,positions,r1,r2,r3', function () { let positions = new p5.Vector(1, 2, 3); myp5.spotLight(255, 0, 255, positions, 0, 1, 0); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); @@ -287,7 +286,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('color,p1,p2,p3,r1,r2,r3', function() { + test('color,p1,p2,p3,r1,r2,r3', function () { let color = myp5.color(255, 0, 255); myp5.spotLight(color, 1, 2, 3, 0, 1, 0); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); @@ -300,7 +299,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('c1,c2,c3,p1,p2,p3,directions,angle', function() { + test('c1,c2,c3,p1,p2,p3,directions,angle', function () { let directions = new p5.Vector(0, 1, 0); myp5.spotLight(255, 0, 255, 1, 2, 3, directions, angle); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); @@ -313,7 +312,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('c1,c2,c3,positions,r1,r2,r3,angle', function() { + test('c1,c2,c3,positions,r1,r2,r3,angle', function () { let positions = new p5.Vector(1, 2, 3); myp5.spotLight(255, 0, 255, positions, 0, 1, 0, angle); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); @@ -326,7 +325,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('color,p1,p2,p3,r1,r2,r3,angle', function() { + test('color,p1,p2,p3,r1,r2,r3,angle', function () { let color = myp5.color(255, 0, 255); myp5.spotLight(color, 1, 2, 3, 0, 1, 0, angle); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); @@ -339,7 +338,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('c1,c2,c3,p1,p2,p3,directions,angle,conc', function() { + test('c1,c2,c3,p1,p2,p3,directions,angle,conc', function () { let directions = new p5.Vector(0, 1, 0); myp5.spotLight(255, 0, 255, 1, 2, 3, directions, angle, conc); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); @@ -352,7 +351,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [7]); }); - test('c1,c2,c3,positions,r1,r2,r3,angle,conc', function() { + test('c1,c2,c3,positions,r1,r2,r3,angle,conc', function () { let positions = new p5.Vector(1, 2, 3); myp5.spotLight(255, 0, 255, positions, 0, 1, 0, angle, conc); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); @@ -365,7 +364,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [7]); }); - test('color,p1,p2,p3,r1,r2,r3,angle,conc', function() { + test('color,p1,p2,p3,r1,r2,r3,angle,conc', function () { let color = myp5.color(255, 0, 255); myp5.spotLight(color, 1, 2, 3, 0, 1, 0, angle, conc); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); @@ -378,7 +377,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [7]); }); - test('c1,c2,c3,p1,p2,p3,r1,r2,r3', function() { + test('c1,c2,c3,p1,p2,p3,r1,r2,r3', function () { myp5.spotLight(255, 0, 255, 1, 2, 3, 0, 1, 0); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); assert.deepEqual( @@ -390,7 +389,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('c1,c2,c3,p1,p2,p3,r1,r2,r3,angle', function() { + test('c1,c2,c3,p1,p2,p3,r1,r2,r3,angle', function () { myp5.spotLight(255, 0, 255, 1, 2, 3, 0, 1, 0, angle); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); assert.deepEqual( @@ -402,7 +401,7 @@ suite('light', function() { assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]); assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]); }); - test('c1,c2,c3,p1,p2,p3,r1,r2,r3,angle,conc', function() { + test('c1,c2,c3,p1,p2,p3,r1,r2,r3,angle,conc', function () { myp5.spotLight(255, 0, 255, 1, 2, 3, 0, 1, 0, angle, 7); assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]); assert.deepEqual( diff --git a/test/unit/webgl/normal.js b/test/unit/webgl/normal.js index cbb7870f70..41af537049 100644 --- a/test/unit/webgl/normal.js +++ b/test/unit/webgl/normal.js @@ -1,37 +1,37 @@ import p5 from '../../../src/app.js'; -suite('', function() { +suite('', function () { var myp5; - beforeAll(function() { - myp5 = new p5(function(p) { - p.setup = function() { + beforeAll(function () { + myp5 = new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100, p.WEBGL); }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('p5.prototype.normal', function() { - test('should be a function', function() { + suite('p5.prototype.normal', function () { + test('should be a function', function () { assert.ok(myp5.normal); assert.typeOf(myp5.normal, 'function'); }); - test('accepts numeric arguments', function() { + test('accepts numeric arguments', function () { assert.doesNotThrow( - function() { + function () { myp5.normal(0, 1, 0); }, Error, 'got unwanted exception' ); }); - test('accepts vector argument', function() { + test('accepts vector argument', function () { assert.doesNotThrow( - function() { + function () { myp5.normal(myp5.createVector(0, 1, 0)); }, Error, diff --git a/test/unit/webgl/p5.Camera.js b/test/unit/webgl/p5.Camera.js index 92cfe69962..b006cc5413 100644 --- a/test/unit/webgl/p5.Camera.js +++ b/test/unit/webgl/p5.Camera.js @@ -2,13 +2,13 @@ import p5 from '../../../src/app.js'; import { HALF_PI } from '../../../src/core/constants'; import '../../js/chai_helpers'; -suite('p5.Camera', function() { +suite('p5.Camera', function () { var myp5; var myCam; var delta = 0.001; // returns values to test which have changed - var getVals = function(cam) { + var getVals = function (cam) { return { ex: cam.eyeX, ey: cam.eyeY, @@ -22,20 +22,20 @@ suite('p5.Camera', function() { }; }; - beforeAll(function() { - myp5 = new p5(function(p) { - p.setup = function() { + beforeAll(function () { + myp5 = new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100, p.WEBGL); myCam = p.createCamera(); }; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - beforeEach(function() { + beforeEach(function () { myp5.angleMode(myp5.RADIANS); // set camera defaults below according to current default values @@ -43,7 +43,7 @@ suite('p5.Camera', function() { myCam.camera( 0, 0, - 100 / 2.0 / Math.tan(Math.PI * 30.0 / 180.0), + 100 / 2.0 / Math.tan((Math.PI * 30.0) / 180.0), 0, 0, 0, @@ -51,43 +51,37 @@ suite('p5.Camera', function() { 1, 0 ); - myCam.perspective( - Math.PI / 3.0, - 1, - myCam.eyeZ / 10.0, - myCam.eyeZ * 10.0 - ); + myCam.perspective(Math.PI / 3.0, 1, myCam.eyeZ / 10.0, myCam.eyeZ * 10.0); myp5.setCamera(myCam); }); - suite('createCamera()', function() { - test('creates a p5.Camera object', function() { + suite('createCamera()', function () { + test('creates a p5.Camera object', function () { var myCam2 = myp5.createCamera(); assert.instanceOf(myCam2, p5.Camera); }); - test('createCamera does not immediately attach to renderer', function() { + test('createCamera does not immediately attach to renderer', function () { var myCam2 = myp5.createCamera(); assert.notEqual(myCam2, myp5._renderer.states.curCamera); }); - test('setCamera() attaches a camera to renderer', function() { + test('setCamera() attaches a camera to renderer', function () { var myCam2 = myp5.createCamera(); myp5.setCamera(myCam2); assert.equal(myCam2, myp5._renderer.states.curCamera); }); }); - suite('Rotation', function() { + suite('Rotation', function () { test('Pan() with positive parameter sets correct matrix w/o changing\ - eyeXYZ or upXYZ', function() { + eyeXYZ or upXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 0.5403022766113281, -0, 0.8414710164070129, 0, - 0, 1, 0, 0, - -0.8414710164070129, 0, 0.5403022766113281, 0, - 72.87352752685547, 0, -46.79154968261719, 1 + 0.5403022766113281, -0, 0.8414710164070129, 0, 0, 1, 0, 0, + -0.8414710164070129, 0, 0.5403022766113281, 0, 72.87352752685547, 0, + -46.79154968261719, 1 ]); myCam.pan(1); @@ -104,14 +98,13 @@ suite('p5.Camera', function() { }); test('Pan() with negative parameter sets correct matrix w/o changing\ - eyeXYZ or upXYZ', function() { + eyeXYZ or upXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 0.5403022766113281, 0, -0.8414710164070129, 0, - -0, 1, 0, 0, - 0.8414710164070129, 0, 0.5403022766113281, 0, - -72.87352752685547, 0, -46.79154968261719, 1 + 0.5403022766113281, 0, -0.8414710164070129, 0, -0, 1, 0, 0, + 0.8414710164070129, 0, 0.5403022766113281, 0, -72.87352752685547, 0, + -46.79154968261719, 1 ]); myCam.pan(-1); @@ -126,14 +119,12 @@ suite('p5.Camera', function() { assert.strictEqual(myCam.upY, orig.uy, 'up Y pos changed'); assert.strictEqual(myCam.upZ, orig.uz, 'up Z pos changed'); }); - test('Pan(0) sets correct matrix w/o changing eyeXYZ or upXYZ', function() { + test('Pan(0) sets correct matrix w/o changing eyeXYZ or upXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, -86.6025390625, 1]); + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -86.6025390625, 1 + ]); myCam.pan(0); @@ -149,14 +140,13 @@ suite('p5.Camera', function() { }); test('Tilt() with positive parameter sets correct Matrix w/o \ - changing eyeXYZ', function() { + changing eyeXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 0.07073719799518585, -0.9974949955940247, 0, - -0, 0.9974949955940247, 0.07073719799518585, 0, - 0, -86.3855972290039, -6.126020908355713, 1 + 1, 0, 0, 0, 0, 0.07073719799518585, -0.9974949955940247, 0, -0, + 0.9974949955940247, 0.07073719799518585, 0, 0, -86.3855972290039, + -6.126020908355713, 1 ]); myCam.tilt(1.5); @@ -169,14 +159,13 @@ suite('p5.Camera', function() { }); test('Tilt() with negative parameter sets correct matrix w/o \ - changing eyeXYZ', function() { + changing eyeXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 0.07073719799518585, 0.9974949955940247, 0, - 0, -0.9974949955940247, 0.07073719799518585, 0, - 0, 86.3855972290039, -6.126020908355713, 1 + 1, 0, 0, 0, 0, 0.07073719799518585, 0.9974949955940247, 0, 0, + -0.9974949955940247, 0.07073719799518585, 0, 0, 86.3855972290039, + -6.126020908355713, 1 ]); myCam.tilt(-1.5); @@ -187,14 +176,11 @@ suite('p5.Camera', function() { assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed'); assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed'); }); - test('Tilt(0) sets correct matrix w/o changing eyeXYZ', function() { + test('Tilt(0) sets correct matrix w/o changing eyeXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, -86.6025390625, 1 + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -86.6025390625, 1 ]); myCam.tilt(0); @@ -207,14 +193,11 @@ suite('p5.Camera', function() { }); test('Roll() with positive parameter sets correct Matrix w/o \ - changing eyeXYZ', function() { + changing eyeXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 0, -1, 0, 0, - 1, 0, 0, 0, - 0, 0, 1, 0, - 0, 0, -86.6025390625, 1 + 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, -86.6025390625, 1 ]); myCam.roll(HALF_PI); @@ -227,14 +210,11 @@ suite('p5.Camera', function() { }); test('Roll() with negative parameter sets correct matrix w/o \ - changing eyeXYZ', function() { + changing eyeXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 0, -1, 0, 0, - 1, 0, 0, 0, - 0, 0, 1, 0, - 0, 0, -86.6025390625, 1 + 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, -86.6025390625, 1 ]); myCam.roll(HALF_PI); @@ -246,14 +226,11 @@ suite('p5.Camera', function() { assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed'); }); - test('Roll(0) sets correct matrix w/o changing upXYZ and eyeXYZ', function() { + test('Roll(0) sets correct matrix w/o changing upXYZ and eyeXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, -86.6025390625, 1 + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -86.6025390625, 1 ]); myCam.roll(0); @@ -270,7 +247,7 @@ suite('p5.Camera', function() { }); test('LookAt() should set centerXYZ without changing eyeXYZ or \ - upXYZ', function() { + upXYZ', function () { var orig = getVals(myCam); myCam.lookAt(10, 20, 30); @@ -289,28 +266,27 @@ suite('p5.Camera', function() { }); }); - suite('Rotation with angleMode(DEGREES)', function() { - beforeEach(function() { + suite('Rotation with angleMode(DEGREES)', function () { + beforeEach(function () { myp5.push(); myp5.angleMode(myp5.DEGREES); }); - afterEach(function() { + afterEach(function () { myp5.pop(); }); test('Pan() with positive parameter sets correct matrix w/o changing\ - eyeXYZ or upXYZ', function() { + eyeXYZ or upXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 0.5403022766113281, -0, 0.8414710164070129, 0, - 0, 1, 0, 0, - -0.8414710164070129, 0, 0.5403022766113281, 0, - 72.87352752685547, 0, -46.79154968261719, 1 + 0.5403022766113281, -0, 0.8414710164070129, 0, 0, 1, 0, 0, + -0.8414710164070129, 0, 0.5403022766113281, 0, 72.87352752685547, 0, + -46.79154968261719, 1 ]); - myCam.pan(1 * 180 / Math.PI); + myCam.pan((1 * 180) / Math.PI); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); @@ -324,16 +300,16 @@ suite('p5.Camera', function() { }); test('Tilt() with positive parameter sets correct Matrix w/o \ - changing eyeXYZ', function() { + changing eyeXYZ', function () { var orig = getVals(myCam); - var expectedMatrix = new Float32Array( - [1, 0, 0, 0, - 0, 0.07073719799518585, -0.9974949955940247, 0, - -0, 0.9974949955940247, 0.07073719799518585, 0, - 0, -86.3855972290039, -6.126020908355713, 1]); + var expectedMatrix = new Float32Array([ + 1, 0, 0, 0, 0, 0.07073719799518585, -0.9974949955940247, 0, -0, + 0.9974949955940247, 0.07073719799518585, 0, 0, -86.3855972290039, + -6.126020908355713, 1 + ]); - myCam.tilt(1.5 * 180 / Math.PI); + myCam.tilt((1.5 * 180) / Math.PI); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); @@ -343,14 +319,11 @@ suite('p5.Camera', function() { }); test('Roll() with positive parameter sets correct Matrix w/o \ - changing eyeXYZ', function() { + changing eyeXYZ', function () { var orig = getVals(myCam); var expectedMatrix = new Float32Array([ - 0, -1, 0, 0, - 1, 0, 0, 0, - 0, 0, 1, 0, - 0, 0, -86.6025390625, 1 + 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, -86.6025390625, 1 ]); myCam.roll(90); @@ -363,10 +336,10 @@ suite('p5.Camera', function() { }); }); - suite('Position / Orientation', function() { - suite('Camera()', function() { + suite('Position / Orientation', function () { + suite('Camera()', function () { test('Camera() with positive parameters sets eye, center, and \ - up XYZ properties', function() { + up XYZ properties', function () { myCam.camera(1, 2, 3, 4, 5, 6, 0, 1, 0); assert.strictEqual(myCam.eyeX, 1); @@ -382,7 +355,7 @@ suite('p5.Camera', function() { assert.strictEqual(myCam.upZ, 0); }); test('Camera() with negative parameters sets eye, center, and \ - up XYZ properties', function() { + up XYZ properties', function () { myCam.camera(-1, -2, -3, -4, -5, -6, 0, -1, 0); assert.strictEqual(myCam.eyeX, -1); @@ -399,72 +372,54 @@ suite('p5.Camera', function() { }); }); - test('Move() with positive parameters sets correct matrix', function() { + test('Move() with positive parameters sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - -1, -2, -89.6025390625, 1 + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1, -2, -89.6025390625, 1 ]); myCam.move(1, 2, 3); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('Move() with negative parameters sets correct matrix', function() { + test('Move() with negative parameters sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 1, 2, -83.6025390625, 1 + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 2, -83.6025390625, 1 ]); myCam.move(-1, -2, -3); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('Move(0,0,0) sets correct matrix', function() { + test('Move(0,0,0) sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, -86.6025390625, 1 + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -86.6025390625, 1 ]); myCam.move(0, 0, 0); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('SetPosition() with positive parameters sets correct matrix', function() { + test('SetPosition() with positive parameters sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - -1, -2, -3, 1 + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1, -2, -3, 1 ]); myCam.setPosition(1, 2, 3); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('SetPosition() with negative parameters sets correct matrix', function() { + test('SetPosition() with negative parameters sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 1, 2, 3, 1 + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 2, 3, 1 ]); myCam.setPosition(-1, -2, -3); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('SetPosition(0,0,0) sets correct matrix', function() { + test('SetPosition(0,0,0) sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); myCam.setPosition(0, 0, 0); @@ -472,7 +427,7 @@ suite('p5.Camera', function() { assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('test for matrix manipulation with set()', function() { + test('test for matrix manipulation with set()', function () { myCam = myp5.createCamera(); myp5.setCamera(myCam); const copyCam = myCam.copy(); @@ -495,86 +450,80 @@ suite('p5.Camera', function() { ); }); - test('_orbit(1,0,0) sets correct matrix', function() { + test('_orbit(1,0,0) sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 0.5403022766113281, -5.1525235865883254e-17, 0.8414709568023682, 0, - 0, 1, 6.123234262925839e-17, 0, - -0.8414709568023682, -3.3083975336675e-17, 0.5403022766113281, 0, - 8.216248374992574e-7, -7.180680227154989e-23, -86.6025390625, 1 + 0.5403022766113281, -5.1525235865883254e-17, 0.8414709568023682, 0, 0, + 1, 6.123234262925839e-17, 0, -0.8414709568023682, -3.3083975336675e-17, + 0.5403022766113281, 0, 8.216248374992574e-7, -7.180680227154989e-23, + -86.6025390625, 1 ]); myCam._orbit(1, 0, 0); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('_orbit(0,1,0) sets correct matrix', function() { + test('_orbit(0,1,0) sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 0.5403022766113281, -0.8414709568023682, 0, - -0, 0.8414709568023682, 0.5403022766113281, 0, - 0, -8.216248374992574e-7, -86.6025390625, 1 + 1, 0, 0, 0, 0, 0.5403022766113281, -0.8414709568023682, 0, -0, + 0.8414709568023682, 0.5403022766113281, 0, 0, -8.216248374992574e-7, + -86.6025390625, 1 ]); myCam._orbit(0, 1, 0); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('_orbit(0,0,1) sets correct matrix', function() { + test('_orbit(0,0,1) sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 6.123234262925839e-17, 0, - 0, -6.123234262925839e-17, 1, 0, - 0, 2.3139252783628976e-21, -866.025390625, 1 + 1, 0, 0, 0, 0, 1, 6.123234262925839e-17, 0, 0, -6.123234262925839e-17, + 1, 0, 0, 2.3139252783628976e-21, -866.025390625, 1 ]); myCam._orbit(0, 0, 1); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('_orbit(-1,0,0) sets correct matrix', function() { + test('_orbit(-1,0,0) sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 0.5403022766113281, 5.1525235865883254e-17, -0.8414709568023682, 0, - -0, 1, 6.123234262925839e-17, 0, - 0.8414709568023682, -3.3083975336675e-17, 0.5403022766113281, 0, - -8.216248374992574e-7, -7.180680227154989e-23, -86.6025390625, 1 + 0.5403022766113281, 5.1525235865883254e-17, -0.8414709568023682, 0, -0, + 1, 6.123234262925839e-17, 0, 0.8414709568023682, -3.3083975336675e-17, + 0.5403022766113281, 0, -8.216248374992574e-7, -7.180680227154989e-23, + -86.6025390625, 1 ]); myCam._orbit(-1, 0, 0); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('_orbit(0,-1,0) sets correct matrix', function() { + test('_orbit(0,-1,0) sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 0.5403022766113281, 0.8414709568023682, 0, - 0, -0.8414709568023682, 0.5403022766113281, 0, - 0, 8.216248374992574e-7, -86.6025390625, 1 + 1, 0, 0, 0, 0, 0.5403022766113281, 0.8414709568023682, 0, 0, + -0.8414709568023682, 0.5403022766113281, 0, 0, 8.216248374992574e-7, + -86.6025390625, 1 ]); myCam._orbit(0, -1, 0); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('_orbit(0,0,-1) sets correct matrix', function() { + test('_orbit(0,0,-1) sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 6.123234262925839e-17, 0, - 0, -6.123234262925839e-17, 1, 0, - 0, 2.3139253036064466e-23, -8.66025447845459, 1 + 1, 0, 0, 0, 0, 1, 6.123234262925839e-17, 0, 0, -6.123234262925839e-17, + 1, 0, 0, 2.3139253036064466e-23, -8.66025447845459, 1 ]); myCam._orbit(0, 0, -1); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('_orbit() does not force up vector to be parallel to y-axis', function() { + test('_orbit() does not force up vector to be parallel to y-axis', function () { // Check the shape of the camera matrix to make sure that the up vector // does not become (0,1,0) or (0,-1,0) after running _orbit() var expectedMatrix = new Float32Array([ -0.2129584103822708, 0.9760860204696655, -0.04364387318491936, 0, - -0.9770612716674805, -0.21274586021900177, 0.009512536227703094, 0, - -0, 0.04466851428151131, 0.9990018606185913, 0, - 2.445493976210855e-7, -1.4983223195486062e-7, -866.025390625, 1 + -0.9770612716674805, -0.21274586021900177, 0.009512536227703094, 0, -0, + 0.04466851428151131, 0.9990018606185913, 0, 2.445493976210855e-7, + -1.4983223195486062e-7, -866.025390625, 1 ]); myCam.camera(100, 100, 100, 0, 0, 0, 0, 0, -1); @@ -582,7 +531,7 @@ suite('p5.Camera', function() { assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('up vector of an arbitrary direction reverses by _orbit(0,PI,0)', function() { + test('up vector of an arbitrary direction reverses by _orbit(0,PI,0)', function () { // Make sure the up vector is reversed by doing _orbit(0, Math.PI, 0) myCam.camera(100, 100, 100, 0, 0, 0, 1, 2, 3); const prevUpX = myCam.upX; @@ -597,7 +546,7 @@ suite('p5.Camera', function() { expect(currUpY).to.be.closeTo(-prevUpY, 0.001); expect(currUpZ).to.be.closeTo(-prevUpZ, 0.001); }); - test('_orbit() ensures myCam.upY switches direction (from 1 to -1) at camPhi <= 0', function() { + test('_orbit() ensures myCam.upY switches direction (from 1 to -1) at camPhi <= 0', function () { // the following should produce the upY with inverted direction(from 1 to -1) // when camPhi changes from positive to negative or zero myCam._orbit(0, -Math.PI, 0); @@ -605,7 +554,7 @@ suite('p5.Camera', function() { // myCam.upY should be -1 assert.equal(myCam.upY, -1); }); - test('_orbit() ensures myCam.upY switches direction (from -1 to 1) at camPhi <= 0', function() { + test('_orbit() ensures myCam.upY switches direction (from -1 to 1) at camPhi <= 0', function () { // the following should produce the upY with inverted direction(from -1 to 1) // when camPhi changes from negative to positive or zero myCam._orbit(0, -Math.PI, 0); @@ -614,7 +563,7 @@ suite('p5.Camera', function() { // myCam.upY should be 1 assert.equal(myCam.upY, 1); }); - test('_orbit() ensures myCam.upY switches direction (from 1 to -1) at camPhi >= PI', function() { + test('_orbit() ensures myCam.upY switches direction (from 1 to -1) at camPhi >= PI', function () { // the following should produce the upY with inverted direction(from 1 to -1) // when camPhi reaches PI myCam._orbit(0, Math.PI, 0); @@ -622,7 +571,7 @@ suite('p5.Camera', function() { // myCam.upY should be -1 assert.equal(myCam.upY, -1); }); - test('_orbit() ensures myCam.upY switches direction (from -1 to 1) at camPhi >= PI', function() { + test('_orbit() ensures myCam.upY switches direction (from -1 to 1) at camPhi >= PI', function () { // the following should produce the upY with inverted direction(from -1 to 1) // when camPhi reaches PI myCam._orbit(0, Math.PI, 0); @@ -631,19 +580,20 @@ suite('p5.Camera', function() { // myCam.upY should be 1 assert.equal(myCam.upY, 1); }); - test('_orbit() ensures camera can do multiple continuous 360deg rotations', function() { + test('_orbit() ensures camera can do multiple continuous 360deg rotations', function () { // the following should produce two camera objects having same properties. myCam._orbit(0, Math.PI, 0); var myCamCopy = myCam.copy(); myCamCopy._orbit(0, Math.PI, 0); myCamCopy._orbit(0, Math.PI, 0); for (let i = 0; i < myCamCopy.cameraMatrix.mat4.length; i++) { - expect( - myCamCopy.cameraMatrix.mat4[i]).to.be.closeTo( - myCam.cameraMatrix.mat4[i], 0.001); + expect(myCamCopy.cameraMatrix.mat4[i]).to.be.closeTo( + myCam.cameraMatrix.mat4[i], + 0.001 + ); } }); - test('Returns to the origin after a 360° rotation regardless of the up vector', function() { + test('Returns to the origin after a 360° rotation regardless of the up vector', function () { // Even if the up vector is not (0,1,0) or (0,-1,0), _orbit() makes sure that // the camera returns to its original position when rotated 360 degrees vertically. myCam.camera(100, 100, 100, 0, 0, 0, 1, 2, 3); @@ -653,32 +603,37 @@ suite('p5.Camera', function() { myCamCopy._orbit(0, Math.PI * 0.01, 0); } for (let i = 0; i < myCamCopy.cameraMatrix.mat4.length; i++) { - expect( - myCamCopy.cameraMatrix.mat4[i]).to.be.closeTo( - myCam.cameraMatrix.mat4[i], 0.001); + expect(myCamCopy.cameraMatrix.mat4[i]).to.be.closeTo( + myCam.cameraMatrix.mat4[i], + 0.001 + ); } }); - test('_orbit() ensures radius > 0', function() { + test('_orbit() ensures radius > 0', function () { // the following should produce two camera objects having same properties. myCam._orbit(0, Math.PI, 0); var myCamCopy = myCam.copy(); myCamCopy._orbit(0, 0, -100); myCam._orbit(0, 0, -250); - assert.deepCloseTo(myCam.cameraMatrix.mat4, myCamCopy.cameraMatrix.mat4, 4, 'deep equal is failing'); + assert.deepCloseTo( + myCam.cameraMatrix.mat4, + myCamCopy.cameraMatrix.mat4, + 4, + 'deep equal is failing' + ); }); - test('_orbitFree(1,0,0) sets correct matrix', function() { + test('_orbitFree(1,0,0) sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 0.5403022766113281, 0, -0.8414709568023682, 0, - -0, 1, 0, 0, - 0.8414709568023682, 0, 0.5403022766113281, 0, - -8.216248374992574e-7, 0, -86.6025390625, 1 + 0.5403022766113281, 0, -0.8414709568023682, 0, -0, 1, 0, 0, + 0.8414709568023682, 0, 0.5403022766113281, 0, -8.216248374992574e-7, 0, + -86.6025390625, 1 ]); myCam._orbitFree(1, 0, 0); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('_orbitFree(0,1,0) sets correct matrix', function() { + test('_orbitFree(0,1,0) sets correct matrix', function () { var expectedMatrix = new Float32Array([ 1, -2.8148363983860944e-17, -5.1525235865883254e-17, 0, -2.8148363983860944e-17, 0.5403022766113281, -0.8414709568023682, 0, @@ -690,19 +645,16 @@ suite('p5.Camera', function() { assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('_orbitFree(0,0,1) sets correct matrix', function() { + test('_orbitFree(0,0,1) sets correct matrix', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, -866.025390625, 1 + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -866.025390625, 1 ]); myCam._orbitFree(0, 0, 1); assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix); }); - test('Rotate camera 360° with _orbitFree() returns it to its original position', function() { + test('Rotate camera 360° with _orbitFree() returns it to its original position', function () { // Rotate the camera 360 degrees in any direction using _orbitFree() // and it will return to its original state. myCam.camera(100, 100, 100, 0, 0, 0, 1, 2, 3); @@ -713,16 +665,17 @@ suite('p5.Camera', function() { myCamCopy._orbitFree(Math.PI * 0.006, Math.PI * 0.008, 0); } for (let i = 0; i < myCamCopy.cameraMatrix.mat4.length; i++) { - expect( - myCamCopy.cameraMatrix.mat4[i]).to.be.closeTo( - myCam.cameraMatrix.mat4[i], 0.001); + expect(myCamCopy.cameraMatrix.mat4[i]).to.be.closeTo( + myCam.cameraMatrix.mat4[i], + 0.001 + ); } }); }); - suite('Projection', function() { - suite('ortho()', function() { - test('ortho() sets renderer uPMatrix', function() { + suite('Projection', function () { + suite('ortho()', function () { + test('ortho() sets renderer uPMatrix', function () { myCam.ortho(-10, 10, -10, 10, 0, 100); assert.deepCloseTo( @@ -731,41 +684,35 @@ suite('p5.Camera', function() { ); }); - test('ortho() sets projection matrix correctly', function() { + test('ortho() sets projection matrix correctly', function () { // expectedMatrix array needs to match Float32Array type of // p5.Camera projMatrix's mat4 array for deepCloseTo to work var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, -1, 0, 0, - 0, 0, -1, 0, - -0, -0, -1, 1 + 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, -0, -0, -1, 1 ]); - myCam.ortho(-1, 1, -1, 1, 0, 2); assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix); }); - test('ortho() with no parameters specified (sets default with added far)', function() { + test('ortho() with no parameters specified (sets default with added far)', function () { var expectedMatrix = new Float32Array([ - 0.019999999552965164, 0, 0, 0, - 0, -0.019999999552965164, 0, 0, - 0, 0, -0.002222222276031971, 0, - -0, -0, -1, 1 + 0.019999999552965164, 0, 0, 0, 0, -0.019999999552965164, 0, 0, 0, 0, + -0.002222222276031971, 0, -0, -0, -1, 1 ]); myCam.ortho(); assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix); }); - test('ortho() sets cameraType to custom', function() { + test('ortho() sets cameraType to custom', function () { myCam.ortho(); assert.equal(myCam.cameraType, 'custom'); }); }); - suite('perspective()', function() { - test('perspective() sets renderer uPMatrix', function() { + suite('perspective()', function () { + test('perspective() sets renderer uPMatrix', function () { myCam.perspective(Math.PI / 3.0, 1, 1, 100); assert.deepCloseTo( @@ -773,12 +720,9 @@ suite('p5.Camera', function() { myp5._renderer.states.uPMatrix.mat4 ); }); - test('perspective() sets projection matrix correctly', function() { + test('perspective() sets projection matrix correctly', function () { var expectedMatrix = new Float32Array([ - 1, 0, 0, 0, - 0, -1, 0, 0, - 0, 0, -3, -1, - 0, 0, -40, 0 + 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -3, -1, 0, 0, -40, 0 ]); myCam.perspective(Math.PI / 2, 1, 10, 20); @@ -786,12 +730,10 @@ suite('p5.Camera', function() { assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix); }); - test('perspective() with no parameters specified (sets default)', function() { + test('perspective() with no parameters specified (sets default)', function () { var expectedMatrix = new Float32Array([ - 16,0,0,0, - 0,-16,0,0, - 0,0,-1.0202020406723022,-1, - 0,0,-161.6161651611328,0 + 16, 0, 0, 0, 0, -16, 0, 0, 0, 0, -1.0202020406723022, -1, 0, 0, + -161.6161651611328, 0 ]); myCam.perspective(); @@ -799,13 +741,13 @@ suite('p5.Camera', function() { assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix); }); - test('perspective() with no parameters sets cameraType to default', function() { + test('perspective() with no parameters sets cameraType to default', function () { myCam.perspective(); assert.equal(myCam.cameraType, 'default'); }); }); - suite('frustum()', function() { - test('frustum() sets renderer uPMatrix', function() { + suite('frustum()', function () { + test('frustum() sets renderer uPMatrix', function () { myCam.frustum(-10, 10, -20, 20, -100, 100); assert.deepCloseTo( @@ -813,27 +755,20 @@ suite('p5.Camera', function() { myp5._renderer.states.uPMatrix.mat4 ); }); - test('frustum() sets projection matrix correctly', function() { - + test('frustum() sets projection matrix correctly', function () { var expectedMatrix = new Float32Array([ - -2, 0, 0, 0, - 0, 2, 0, 0, - 0, 0, -0, -1, - 0, 0, 2, 0 + -2, 0, 0, 0, 0, 2, 0, 0, 0, 0, -0, -1, 0, 0, 2, 0 ]); - myCam.frustum(-1, 1, -1, 1, -2, 2); assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix); }); - test('frustum() with no parameters specified (sets default)', function() { + test('frustum() with no parameters specified (sets default)', function () { var expectedMatrix = new Float32Array([ - 16,0,0,0, - 0,16,0,0, - 0,-0,-1.0202020406723022,-1, - 0,0,-161.6161651611328,0 + 16, 0, 0, 0, 0, 16, 0, 0, 0, -0, -1.0202020406723022, -1, 0, 0, + -161.6161651611328, 0 ]); myCam.frustum(); @@ -841,21 +776,23 @@ suite('p5.Camera', function() { assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix); }); - test('frustum() sets cameraType to custom', function() { + test('frustum() sets cameraType to custom', function () { myCam.frustum(-1, 1, -1, 1, -2, 2); assert.equal(myCam.cameraType, 'custom'); }); }); }); - suite('slerp()', function() { - const expectCameraMatricesAreClose = function(cam0, cam1) { + suite('slerp()', function () { + const expectCameraMatricesAreClose = function (cam0, cam1) { for (let i = 0; i < cam0.cameraMatrix.mat4.length; i++) { - expect(cam0.cameraMatrix.mat4[i]) - .to.be.closeTo(cam1.cameraMatrix.mat4[i], 0.001); + expect(cam0.cameraMatrix.mat4[i]).to.be.closeTo( + cam1.cameraMatrix.mat4[i], + 0.001 + ); } }; - test('if amt is 0 or 1, the argument camera is set', function() { + test('if amt is 0 or 1, the argument camera is set', function () { myCam = myp5.createCamera(); const cam0 = myCam.copy(); cam0.camera(20, 30, 40, 2, 3, 8, 0, 9, -6); @@ -870,7 +807,7 @@ suite('p5.Camera', function() { myCam.slerp(cam0, cam1, 1); assert.deepCloseTo(myCam.cameraMatrix.mat4, cam1.cameraMatrix.mat4); }); - test('Behavior of slerp() for camera moved by pan()', function() { + test('Behavior of slerp() for camera moved by pan()', function () { myCam = myp5.createCamera(); const cam0 = myCam.copy(); const cam1 = myCam.copy(); @@ -885,14 +822,14 @@ suite('p5.Camera', function() { cam4.pan(Math.PI * 1.1); // Compare these views with the view set by slerp(). - myCam.slerp(cam0, cam1, 4/9); + myCam.slerp(cam0, cam1, 4 / 9); expectCameraMatricesAreClose(myCam, cam2); - myCam.slerp(cam0, cam1, -2/9); + myCam.slerp(cam0, cam1, -2 / 9); expectCameraMatricesAreClose(myCam, cam3); - myCam.slerp(cam0, cam1, 11/9); + myCam.slerp(cam0, cam1, 11 / 9); expectCameraMatricesAreClose(myCam, cam4); }); - test('Behavior of slerp() for camera moved by tilt()', function() { + test('Behavior of slerp() for camera moved by tilt()', function () { myCam = myp5.createCamera(); const cam0 = myCam.copy(); const cam1 = myCam.copy(); @@ -907,14 +844,14 @@ suite('p5.Camera', function() { cam4.tilt(Math.PI * 0.4); // Compare these views with the view set by slerp(). - myCam.slerp(cam0, cam1, 1/3); + myCam.slerp(cam0, cam1, 1 / 3); expectCameraMatricesAreClose(myCam, cam2); - myCam.slerp(cam0, cam1, -2/3); + myCam.slerp(cam0, cam1, -2 / 3); expectCameraMatricesAreClose(myCam, cam3); - myCam.slerp(cam0, cam1, 4/3); + myCam.slerp(cam0, cam1, 4 / 3); expectCameraMatricesAreClose(myCam, cam4); }); - test('Behavior of slerp() for camera moved by _orbit()', function() { + test('Behavior of slerp() for camera moved by _orbit()', function () { myCam = myp5.createCamera(); const cam0 = myCam.copy(); const cam1 = myCam.copy(); @@ -939,21 +876,21 @@ suite('p5.Camera', function() { cam8._orbit(0, Math.PI * 1.1, 0.11); // Compare these views with the view set by slerp(). - myCam.slerp(cam0, cam1, 3/8); + myCam.slerp(cam0, cam1, 3 / 8); expectCameraMatricesAreClose(myCam, cam3); - myCam.slerp(cam0, cam1, -7/8); + myCam.slerp(cam0, cam1, -7 / 8); expectCameraMatricesAreClose(myCam, cam4); - myCam.slerp(cam0, cam1, 11/8); + myCam.slerp(cam0, cam1, 11 / 8); expectCameraMatricesAreClose(myCam, cam5); - myCam.slerp(cam0, cam2, 3/7); + myCam.slerp(cam0, cam2, 3 / 7); expectCameraMatricesAreClose(myCam, cam6); - myCam.slerp(cam0, cam2, -4/7); + myCam.slerp(cam0, cam2, -4 / 7); expectCameraMatricesAreClose(myCam, cam7); - myCam.slerp(cam0, cam2, 11/7); + myCam.slerp(cam0, cam2, 11 / 7); expectCameraMatricesAreClose(myCam, cam8); }); - test('Behavior of slerp() for camera moved by _orbitFree()', function() { + test('Behavior of slerp() for camera moved by _orbitFree()', function () { myCam = myp5.createCamera(); const cam0 = myCam.copy(); const cam1 = myCam.copy(); @@ -968,14 +905,14 @@ suite('p5.Camera', function() { cam4._orbitFree(Math.PI * 1.2, Math.PI * 0.9, 0.12); // Compare these views with the view set by slerp(). - myCam.slerp(cam0, cam1, 1/2); + myCam.slerp(cam0, cam1, 1 / 2); expectCameraMatricesAreClose(myCam, cam2); - myCam.slerp(cam0, cam1, -1/4); + myCam.slerp(cam0, cam1, -1 / 4); expectCameraMatricesAreClose(myCam, cam3); - myCam.slerp(cam0, cam1, 3/2); + myCam.slerp(cam0, cam1, 3 / 2); expectCameraMatricesAreClose(myCam, cam4); }); - test('if viewpoints of cam0, cam1 are same, all the same.', function() { + test('if viewpoints of cam0, cam1 are same, all the same.', function () { myCam = myp5.createCamera(); const cam0 = myCam.copy(); cam0.camera(22, 37, 49, 0, 1, 2, 3, 4, -1); @@ -990,7 +927,7 @@ suite('p5.Camera', function() { expect(myCam.eyeZ).to.be.closeTo(cam0.eyeZ, 0.00001); } }); - test('if center of cam0, cam1 are same, all the same.', function() { + test('if center of cam0, cam1 are same, all the same.', function () { myCam = myp5.createCamera(); const cam0 = myCam.copy(); cam0.camera(0, 1, 2, 22, 37, 49, 3, 4, -1); @@ -1005,7 +942,7 @@ suite('p5.Camera', function() { expect(myCam.centerZ).to.be.closeTo(cam0.centerZ, 0.00001); } }); - test('if all camera is ortho, 0,5 component is interpolated', function() { + test('if all camera is ortho, 0,5 component is interpolated', function () { myCam = myp5.createCamera(); myCam.ortho(-50, 50, -50, 50, 0, 1000); const cam0 = myCam.copy(); @@ -1020,14 +957,18 @@ suite('p5.Camera', function() { // to make sure it is logarithmically interpolated. const p0_0 = cam0.projMatrix.mat4[0]; const p1_0 = cam1.projMatrix.mat4[0]; - expect(myCam.projMatrix.mat4[0]) - .to.be.closeTo(p0_0 * Math.pow(p1_0 / p0_0, 0.3), 0.00001); + expect(myCam.projMatrix.mat4[0]).to.be.closeTo( + p0_0 * Math.pow(p1_0 / p0_0, 0.3), + 0.00001 + ); const p0_5 = cam0.projMatrix.mat4[5]; const p1_5 = cam1.projMatrix.mat4[5]; - expect(myCam.projMatrix.mat4[5]) - .to.be.closeTo(p0_5 * Math.pow(p1_5 / p0_5, 0.3), 0.00001); + expect(myCam.projMatrix.mat4[5]).to.be.closeTo( + p0_5 * Math.pow(p1_5 / p0_5, 0.3), + 0.00001 + ); }); - test('Preventing bugs from occurring by changes in slerp spec', function() { + test('Preventing bugs from occurring by changes in slerp spec', function () { myCam = myp5.createCamera(); const cam0 = myp5.createCamera(); const cam1 = myp5.createCamera(); @@ -1044,8 +985,8 @@ suite('p5.Camera', function() { }); }); - suite('Helper Functions', function() { - test('copy() returns a new p5.Camera object', function() { + suite('Helper Functions', function () { + test('copy() returns a new p5.Camera object', function () { var newCam = myCam.copy(); assert.instanceOf(newCam, p5.Camera); @@ -1066,7 +1007,7 @@ suite('p5.Camera', function() { assert.deepCloseTo(newCam.projMatrix.mat4, myCam.projMatrix.mat4); }); - test('_getLocalAxes() returns three normalized, orthogonal vectors', function() { + test('_getLocalAxes() returns three normalized, orthogonal vectors', function () { var local = myCam._getLocalAxes(); // assert that all returned values are numbers @@ -1108,15 +1049,15 @@ suite('p5.Camera', function() { }); }); - suite('RendererGL Current Camera', function() { + suite('RendererGL Current Camera', function () { test('_isActive() returns true for a camera created with createCamera(),\ - and false for another p5.Camera', function() { + and false for another p5.Camera', function () { var myCam2 = myp5.createCamera(); myp5.setCamera(myCam2); assert.isTrue(myCam2._isActive()); assert.isFalse(myCam._isActive()); }); - test('setCamera() correctly sets RendererGL current camera', function() { + test('setCamera() correctly sets RendererGL current camera', function () { var myCam2 = myp5.createCamera(); var myCam3 = myp5.createCamera(); myp5.setCamera(myCam2); @@ -1126,7 +1067,7 @@ suite('p5.Camera', function() { myp5.setCamera(myCam); assert.deepCloseTo(myCam, myp5._renderer.states.curCamera); }); - test("Camera's Renderer is correctly set after setAttributes", function() { + test("Camera's Renderer is correctly set after setAttributes", function () { var myCam2 = myp5.createCamera(); assert.deepCloseTo(myCam2, myp5._renderer.states.curCamera); myp5.setAttributes('antialias', true); @@ -1134,8 +1075,8 @@ suite('p5.Camera', function() { }); }); - suite('Camera attributes after resizing', function() { - test('Camera position is the same', function() { + suite('Camera attributes after resizing', function () { + test('Camera position is the same', function () { myp5.createCanvas(1, 1, myp5.WEBGL); myp5.noStroke(); myp5.pixelDensity(1); @@ -1166,7 +1107,7 @@ suite('p5.Camera', function() { assert.equal(cam.eyeX, 10); }); - test('Camera rotation is the same', function() { + test('Camera rotation is the same', function () { myp5.createCanvas(1, 1, myp5.WEBGL); myp5.noStroke(); myp5.pixelDensity(1); diff --git a/test/unit/webgl/p5.Framebuffer.js b/test/unit/webgl/p5.Framebuffer.js index 4ff9b128cb..4147edb174 100644 --- a/test/unit/webgl/p5.Framebuffer.js +++ b/test/unit/webgl/p5.Framebuffer.js @@ -8,9 +8,9 @@ const mockAnchorElement = vi.mockObject({ }); const originalCreateElement = document.createElement; vi.spyOn(document, 'createElement').mockImplementation((...args) => { - if(args[0] !== 'a'){ + if (args[0] !== 'a') { return originalCreateElement.apply(document, args); - }else{ + } else { return mockAnchorElement; } }); @@ -31,29 +31,29 @@ expect.extend({ } }); -suite('p5.Framebuffer', function() { +suite('p5.Framebuffer', function () { let myp5; let prevPixelRatio; - beforeAll(function() { + beforeAll(function () { prevPixelRatio = window.devicePixelRatio; window.devicePixelRatio = 1; - myp5 = new p5(function(p) { - p.setup = function() {}; - p.draw = function() {}; + myp5 = new p5(function (p) { + p.setup = function () {}; + p.draw = function () {}; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); window.devicePixelRatio = prevPixelRatio; }); - afterEach(function(){ + afterEach(function () { vi.clearAllMocks(); }); - suite('formats and channels', function() { + suite('formats and channels', function () { function testWithConfiguration( version, format, @@ -61,44 +61,35 @@ suite('p5.Framebuffer', function() { antialias, depth ) { - test( - `framebuffers work with WebGL ${version}, ${format} ${channels} ${depth || 'no'} depth ${antialias ? ' antialiased' : ''}`, - function() { - myp5.createCanvas(10, 10, myp5.WEBGL); - myp5.setAttributes({ version }); - - // Draw a box to the framebuffer - const fbo = myp5.createFramebuffer({ - format, - channels, - antialias, - depth: depth !== null, - depthFormat: depth - }); - fbo.draw(() => { - myp5.background(255); - myp5.noStroke(); - myp5.fill('red'); - myp5.box(5, 5, 5); - }); - - // Draw the framebuffer to the canvas - myp5.background(0); + test(`framebuffers work with WebGL ${version}, ${format} ${channels} ${depth || 'no'} depth ${antialias ? ' antialiased' : ''}`, function () { + myp5.createCanvas(10, 10, myp5.WEBGL); + myp5.setAttributes({ version }); + + // Draw a box to the framebuffer + const fbo = myp5.createFramebuffer({ + format, + channels, + antialias, + depth: depth !== null, + depthFormat: depth + }); + fbo.draw(() => { + myp5.background(255); myp5.noStroke(); - myp5.texture(fbo); - myp5.plane(myp5.width, -myp5.height); + myp5.fill('red'); + myp5.box(5, 5, 5); + }); - // Make sure it drew - assert.deepEqual( - myp5.get(0, 0), - [255, 255, 255, 255] - ); - assert.deepEqual( - myp5.get(5, 5), - [255, 0, 0, 255] - ); - } - ); + // Draw the framebuffer to the canvas + myp5.background(0); + myp5.noStroke(); + myp5.texture(fbo); + myp5.plane(myp5.width, -myp5.height); + + // Make sure it drew + assert.deepEqual(myp5.get(0, 0), [255, 255, 255, 255]); + assert.deepEqual(myp5.get(5, 5), [255, 0, 0, 255]); + }); } const versions = [1, 2]; @@ -125,7 +116,7 @@ suite('p5.Framebuffer', function() { } }); - suite('sizing', function() { + suite('sizing', function () { let glStub; afterEach(() => { @@ -134,7 +125,7 @@ suite('p5.Framebuffer', function() { } }); - test('auto-sized framebuffers change size with their canvas', function() { + test('auto-sized framebuffers change size with their canvas', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.pixelDensity(1); const fbo = myp5.createFramebuffer(); @@ -154,7 +145,7 @@ suite('p5.Framebuffer', function() { expect(fbo.color.rawTexture()).not.to.equal(oldTexture); }); - test('manually-sized framebuffers do not change size with their canvas', function() { + test('manually-sized framebuffers do not change size with their canvas', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.pixelDensity(3); const fbo = myp5.createFramebuffer({ width: 20, height: 20, density: 1 }); @@ -174,7 +165,7 @@ suite('p5.Framebuffer', function() { expect(fbo.color.rawTexture().texture).to.equal(oldTexture.texture); }); - test('manually-sized framebuffers can be made auto-sized', function() { + test('manually-sized framebuffers can be made auto-sized', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.pixelDensity(3); const fbo = myp5.createFramebuffer({ width: 20, height: 20, density: 1 }); @@ -198,10 +189,10 @@ suite('p5.Framebuffer', function() { expect(fbo.color.rawTexture()).not.to.equal(oldTexture); }); - suite('resizing', function() { + suite('resizing', function () { let fbo; let oldTexture; - beforeEach(function() { + beforeEach(function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.pixelDensity(1); fbo = myp5.createFramebuffer(); @@ -211,7 +202,7 @@ suite('p5.Framebuffer', function() { fbo.pixelDensity(2); }); - test('framebuffers can be resized', function() { + test('framebuffers can be resized', function () { expect(fbo.width).to.equal(5); expect(fbo.height).to.equal(15); expect(fbo.density).to.equal(2); @@ -220,7 +211,7 @@ suite('p5.Framebuffer', function() { expect(fbo.color.rawTexture()).not.to.equal(oldTexture); }); - test('resizing a framebuffer turns off auto-sizing', function() { + test('resizing a framebuffer turns off auto-sizing', function () { oldTexture = fbo.color.rawTexture(); myp5.resizeCanvas(20, 20); @@ -234,7 +225,7 @@ suite('p5.Framebuffer', function() { expect(fbo.color.rawTexture().texture).to.equal(oldTexture.texture); }); - test('resizes the framebuffer by createFramebuffer based on max texture size', function() { + test('resizes the framebuffer by createFramebuffer based on max texture size', function () { myp5.createCanvas(10, 10, myp5.WEBGL); delete myp5._renderer._maxTextureSize; glStub = vi.spyOn(myp5._renderer, '_getMaxTextureSize'); @@ -246,7 +237,7 @@ suite('p5.Framebuffer', function() { expect(fbo.height).to.equal(100); }); - test('resizes the framebuffer by resize method based on max texture size', function() { + test('resizes the framebuffer by resize method based on max texture size', function () { myp5.createCanvas(10, 10, myp5.WEBGL); delete myp5._renderer._maxTextureSize; glStub = vi.spyOn(myp5._renderer, '_getMaxTextureSize'); @@ -261,7 +252,7 @@ suite('p5.Framebuffer', function() { }); }); - suite('rendering', function() { + suite('rendering', function () { function setupAndReturnFramebuffer() { myp5.createCanvas(10, 10, myp5.WEBGL); @@ -277,7 +268,7 @@ suite('p5.Framebuffer', function() { return fbo; } - test('rendering works with fbo.color as a texture', function() { + test('rendering works with fbo.color as a texture', function () { const fbo = setupAndReturnFramebuffer(); // Draw the framebuffer to the canvas @@ -286,13 +277,10 @@ suite('p5.Framebuffer', function() { myp5.texture(fbo.color); myp5.plane(myp5.width, -myp5.height); - assert.deepEqual( - myp5.get(5, 5), - [255, 0, 0, 255] - ); + assert.deepEqual(myp5.get(5, 5), [255, 0, 0, 255]); }); - test('rendering works with fbo as a texture', function() { + test('rendering works with fbo as a texture', function () { const fbo = setupAndReturnFramebuffer(); // Draw the framebuffer to the canvas @@ -301,13 +289,10 @@ suite('p5.Framebuffer', function() { myp5.texture(fbo); myp5.plane(myp5.width, -myp5.height); - assert.deepEqual( - myp5.get(5, 5), - [255, 0, 0, 255] - ); + assert.deepEqual(myp5.get(5, 5), [255, 0, 0, 255]); }); - test('rendering works with fbo.depth as a texture', function() { + test('rendering works with fbo.depth as a texture', function () { const fbo = setupAndReturnFramebuffer(); // Draw the framebuffer to the canvas @@ -321,7 +306,7 @@ suite('p5.Framebuffer', function() { }); }); - test('Framebuffers work on p5.Graphics', function() { + test('Framebuffers work on p5.Graphics', function () { const graphic = myp5.createGraphics(10, 10, myp5.WEBGL); // Draw a box to the framebuffer @@ -340,18 +325,12 @@ suite('p5.Framebuffer', function() { graphic.plane(graphic.width, -graphic.height); // Make sure it drew - assert.deepEqual( - graphic.get(0, 0), - [255, 255, 255, 255] - ); - assert.deepEqual( - graphic.get(5, 5), - [255, 0, 0, 255] - ); + assert.deepEqual(graphic.get(0, 0), [255, 255, 255, 255]); + assert.deepEqual(graphic.get(5, 5), [255, 0, 0, 255]); }); - suite('remove()', function() { - test('remove() cleans up textures', function() { + suite('remove()', function () { + test('remove() cleans up textures', function () { myp5.createCanvas(10, 10, myp5.WEBGL); const fbo = myp5.createFramebuffer(); const numTextures = myp5._renderer.textures.size; @@ -359,28 +338,25 @@ suite('p5.Framebuffer', function() { expect(myp5._renderer.textures.size).to.equal(numTextures - 2); }); - test( - 'remove() cleans up textures when the framebuffer has no depth', - function() { - myp5.createCanvas(10, 10, myp5.WEBGL); - const fbo = myp5.createFramebuffer({ depth: false }); - const numTextures = myp5._renderer.textures.size; - fbo.remove(); - expect(myp5._renderer.textures.size).to.equal(numTextures - 1); - } - ); + test('remove() cleans up textures when the framebuffer has no depth', function () { + myp5.createCanvas(10, 10, myp5.WEBGL); + const fbo = myp5.createFramebuffer({ depth: false }); + const numTextures = myp5._renderer.textures.size; + fbo.remove(); + expect(myp5._renderer.textures.size).to.equal(numTextures - 1); + }); }); - suite('defaultCamera', function() { + suite('defaultCamera', function () { let fbo; - beforeEach(function() { + beforeEach(function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.pixelDensity(1); fbo = myp5.createFramebuffer({ width: 5, height: 15 }); }); - suite('the default camera', function() { - test('it uses the aspect ratio of the framebuffer', function() { + suite('the default camera', function () { + test('it uses the aspect ratio of the framebuffer', function () { expect(fbo.defaultCamera.aspectRatio).to.equal(5 / 15); expect(fbo.defaultCamera.cameraFOV).to.be.closeTo( 2.0 * Math.atan(15 / 2 / 800), @@ -388,18 +364,32 @@ suite('p5.Framebuffer', function() { ); const z = -800; const expectedCameraMatrix = [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, z, 1 + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + z, + 1 ]; for (let i = 0; i < expectedCameraMatrix.length; i++) { - expect(fbo.defaultCamera.cameraMatrix.mat4[i]) - .to.be.closeTo(expectedCameraMatrix[i], 0.01); + expect(fbo.defaultCamera.cameraMatrix.mat4[i]).to.be.closeTo( + expectedCameraMatrix[i], + 0.01 + ); } }); - test('it updates the aspect ratio after resizing', function() { + test('it updates the aspect ratio after resizing', function () { fbo.resize(20, 10); expect(fbo.defaultCamera.aspectRatio).to.equal(2); expect(fbo.defaultCamera.cameraFOV).to.be.closeTo( @@ -408,34 +398,48 @@ suite('p5.Framebuffer', function() { ); const z = -800; const expectedCameraMatrix = [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, z, 1 + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + z, + 1 ]; for (let i = 0; i < expectedCameraMatrix.length; i++) { - expect(fbo.defaultCamera.cameraMatrix.mat4[i]) - .to.be.closeTo(expectedCameraMatrix[i], 0.01); + expect(fbo.defaultCamera.cameraMatrix.mat4[i]).to.be.closeTo( + expectedCameraMatrix[i], + 0.01 + ); } }); }); }); - suite('get()', function() { - test('get(x, y) loads a pixel', function() { + suite('get()', function () { + test('get(x, y) loads a pixel', function () { myp5.createCanvas(20, 20, myp5.WEBGL); const fbo = myp5.createFramebuffer(); fbo.draw(() => { myp5.noStroke(); myp5.push(); - myp5.translate(-myp5.width/2, -myp5.height/2); + myp5.translate(-myp5.width / 2, -myp5.height / 2); myp5.fill('red'); myp5.sphere(5); myp5.pop(); myp5.push(); - myp5.translate(myp5.width/2, myp5.height/2); + myp5.translate(myp5.width / 2, myp5.height / 2); myp5.fill('blue'); myp5.sphere(5); myp5.pop(); @@ -446,20 +450,20 @@ suite('p5.Framebuffer', function() { assert.deepEqual(fbo.get(19, 19), [0, 0, 255, 255]); }); - test('get() creates a p5.Image with equivalent pixels', function() { + test('get() creates a p5.Image with equivalent pixels', function () { myp5.createCanvas(20, 20, myp5.WEBGL); const fbo = myp5.createFramebuffer(); fbo.draw(() => { myp5.noStroke(); myp5.push(); - myp5.translate(-myp5.width/2, -myp5.height/2); + myp5.translate(-myp5.width / 2, -myp5.height / 2); myp5.fill('red'); myp5.sphere(5); myp5.pop(); myp5.push(); - myp5.translate(myp5.width/2, myp5.height/2); + myp5.translate(myp5.width / 2, myp5.height / 2); myp5.fill('blue'); myp5.sphere(5); myp5.pop(); @@ -476,7 +480,7 @@ suite('p5.Framebuffer', function() { } }); - test('get() creates a p5.Image matching the source pixel density', function() { + test('get() creates a p5.Image matching the source pixel density', function () { const mainCanvas = myp5.createCanvas(20, 20, myp5.WEBGL); myp5.pixelDensity(2); const fbo = myp5.createFramebuffer(); @@ -485,13 +489,13 @@ suite('p5.Framebuffer', function() { myp5.background(255); myp5.push(); - myp5.translate(-myp5.width/2, -myp5.height/2); + myp5.translate(-myp5.width / 2, -myp5.height / 2); myp5.fill('red'); myp5.sphere(5); myp5.pop(); myp5.push(); - myp5.translate(myp5.width/2, myp5.height/2); + myp5.translate(myp5.width / 2, myp5.height / 2); myp5.fill('blue'); myp5.sphere(5); myp5.pop(); @@ -512,45 +516,42 @@ suite('p5.Framebuffer', function() { }); }); - test( - 'loadPixels works in arbitrary order for multiple framebuffers', - function() { - myp5.createCanvas(20, 20, myp5.WEBGL); - const fbo1 = myp5.createFramebuffer(); - const fbo2 = myp5.createFramebuffer(); - - fbo1.loadPixels(); - fbo2.loadPixels(); - for (let i = 0; i < fbo1.pixels.length; i += 4) { - // Set everything red - fbo1.pixels[i] = 255; - fbo1.pixels[i + 1] = 0; - fbo1.pixels[i + 2] = 0; - fbo1.pixels[i + 3] = 255; - } - for (let i = 0; i < fbo2.pixels.length; i += 4) { - // Set everything blue - fbo2.pixels[i] = 0; - fbo2.pixels[i + 1] = 0; - fbo2.pixels[i + 2] = 255; - fbo2.pixels[i + 3] = 255; - } - fbo2.updatePixels(); - fbo1.updatePixels(); + test('loadPixels works in arbitrary order for multiple framebuffers', function () { + myp5.createCanvas(20, 20, myp5.WEBGL); + const fbo1 = myp5.createFramebuffer(); + const fbo2 = myp5.createFramebuffer(); + + fbo1.loadPixels(); + fbo2.loadPixels(); + for (let i = 0; i < fbo1.pixels.length; i += 4) { + // Set everything red + fbo1.pixels[i] = 255; + fbo1.pixels[i + 1] = 0; + fbo1.pixels[i + 2] = 0; + fbo1.pixels[i + 3] = 255; + } + for (let i = 0; i < fbo2.pixels.length; i += 4) { + // Set everything blue + fbo2.pixels[i] = 0; + fbo2.pixels[i + 1] = 0; + fbo2.pixels[i + 2] = 255; + fbo2.pixels[i + 3] = 255; + } + fbo2.updatePixels(); + fbo1.updatePixels(); - myp5.imageMode(myp5.CENTER); + myp5.imageMode(myp5.CENTER); - myp5.clear(); - myp5.image(fbo1, 0, 0); - assert.deepEqual(myp5.get(0, 0), [255, 0, 0, 255]); + myp5.clear(); + myp5.image(fbo1, 0, 0); + assert.deepEqual(myp5.get(0, 0), [255, 0, 0, 255]); - myp5.clear(); - myp5.image(fbo2, 0, 0); - assert.deepEqual(myp5.get(0, 0), [0, 0, 255, 255]); - } - ); + myp5.clear(); + myp5.image(fbo2, 0, 0); + assert.deepEqual(myp5.get(0, 0), [0, 0, 255, 255]); + }); - test('Strokes work on and off of framebuffers', function() { + test('Strokes work on and off of framebuffers', function () { myp5.createCanvas(20, 20, myp5.WEBGL); const fbo = myp5.createFramebuffer(); @@ -561,7 +562,7 @@ suite('p5.Framebuffer', function() { myp5.strokeWeight(20); myp5.beginShape(); for (let i = 0; i < 20; i++) { - const angle = i/20*myp5.TWO_PI; + const angle = (i / 20) * myp5.TWO_PI; myp5.vertex( 100 * myp5.cos(angle), // Add an offset to make sure results don't get flipped @@ -582,19 +583,19 @@ suite('p5.Framebuffer', function() { assert.deepEqual(fboPixels, mainPixels); }); - suite('nesting', function() { + suite('nesting', function () { for (const antialias of [false, true]) { - suite(`with antialiasing ${antialias ? 'on' : 'off'}`, function() { + suite(`with antialiasing ${antialias ? 'on' : 'off'}`, function () { let fbo1; let fbo2; - beforeEach(function() { + beforeEach(function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.pixelDensity(1); fbo1 = myp5.createFramebuffer({ antialias }); fbo2 = myp5.createFramebuffer({ antialias }); }); - test('one can nest active framebuffers', function() { + test('one can nest active framebuffers', function () { fbo1.begin(); myp5.fill('red'); myp5.circle(0, 0, 10); @@ -610,8 +611,8 @@ suite('p5.Framebuffer', function() { assert.deepEqual(fbo2.get(5, 5), [0, 0, 255, 255]); }); - test('end() in the wrong order throws an error', function() { - expect(function() { + test('end() in the wrong order throws an error', function () { + expect(function () { fbo1.begin(); fbo2.begin(); fbo1.end(); @@ -619,7 +620,7 @@ suite('p5.Framebuffer', function() { }).to.throw(/another Framebuffer is active/); }); - test('one can read a nested framebuffer', function() { + test('one can read a nested framebuffer', function () { myp5.imageMode(myp5.CENTER); fbo1.begin(); @@ -639,37 +640,33 @@ suite('p5.Framebuffer', function() { } }); - suite('texture filtering', function() { - test('can create a framebuffer that uses NEAREST texture filtering', - function() { - myp5.createCanvas(10, 10, myp5.WEBGL); - const fbo = myp5.createFramebuffer({ - textureFiltering: myp5.NEAREST - }); - - assert.equal( - fbo.color.framebuffer.colorP5Texture.minFilter, myp5.NEAREST - ); - assert.equal( - fbo.color.framebuffer.colorP5Texture.magFilter, myp5.NEAREST - ); + suite('texture filtering', function () { + test('can create a framebuffer that uses NEAREST texture filtering', function () { + myp5.createCanvas(10, 10, myp5.WEBGL); + const fbo = myp5.createFramebuffer({ + textureFiltering: myp5.NEAREST }); - test('can create a framebuffer that uses LINEAR texture filtering', - function() { - myp5.createCanvas(10, 10, myp5.WEBGL); - // LINEAR should be the default - const fbo = myp5.createFramebuffer({}); - assert.equal( - fbo.color.framebuffer.colorP5Texture.minFilter, myp5.LINEAR - ); - assert.equal( - fbo.color.framebuffer.colorP5Texture.magFilter, myp5.LINEAR - ); - }); + assert.equal( + fbo.color.framebuffer.colorP5Texture.minFilter, + myp5.NEAREST + ); + assert.equal( + fbo.color.framebuffer.colorP5Texture.magFilter, + myp5.NEAREST + ); + }); + test('can create a framebuffer that uses LINEAR texture filtering', function () { + myp5.createCanvas(10, 10, myp5.WEBGL); + // LINEAR should be the default + const fbo = myp5.createFramebuffer({}); + + assert.equal(fbo.color.framebuffer.colorP5Texture.minFilter, myp5.LINEAR); + assert.equal(fbo.color.framebuffer.colorP5Texture.magFilter, myp5.LINEAR); + }); }); - suite('saveCanvas', function() { + suite('saveCanvas', function () { test('should download a png file', async () => { myp5.createCanvas(100, 100, myp5.WEBGL); const fbo = myp5.createFramebuffer(); diff --git a/test/unit/webgl/p5.Geometry.js b/test/unit/webgl/p5.Geometry.js index f48ecbef2e..44b492b859 100644 --- a/test/unit/webgl/p5.Geometry.js +++ b/test/unit/webgl/p5.Geometry.js @@ -1,35 +1,35 @@ import p5 from '../../../src/app.js'; import { vi } from 'vitest'; -suite('p5.Geometry', function() { +suite('p5.Geometry', function () { let myp5; - beforeAll(function() { - myp5 = new p5(function(p) { - p.setup = function() {}; - p.draw = function() {}; + beforeAll(function () { + myp5 = new p5(function (p) { + p.setup = function () {}; + p.draw = function () {}; }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('generating edge geometry', function() { + suite('generating edge geometry', function () { let geom; - beforeEach(function() { + beforeEach(function () { geom = new p5.Geometry(undefined, undefined, undefined, myp5._renderer); vi.spyOn(geom, '_addCap'); vi.spyOn(geom, '_addJoin'); vi.spyOn(geom, '_addSegment'); }); - afterEach(function() { + afterEach(function () { vi.restoreAllMocks(); }); - test('single polyline', function() { + test('single polyline', function () { geom.vertices.push( myp5.createVector(0, 0), myp5.createVector(0, 100), @@ -44,7 +44,7 @@ suite('p5.Geometry', function() { expect(geom._addJoin).toHaveBeenCalledTimes(2); }); - test('straight line', function() { + test('straight line', function () { geom.vertices.push( myp5.createVector(0, 0), myp5.createVector(0, 100), @@ -59,7 +59,7 @@ suite('p5.Geometry', function() { expect(geom._addJoin).toHaveBeenCalledTimes(0); }); - test('two disconnected polylines', function() { + test('two disconnected polylines', function () { geom.vertices.push( myp5.createVector(0, 0), myp5.createVector(0, 100), @@ -74,7 +74,7 @@ suite('p5.Geometry', function() { expect(geom._addJoin).toHaveBeenCalledTimes(0); }); - test('polyline that loops back', function() { + test('polyline that loops back', function () { geom.vertices.push( myp5.createVector(0, 0), myp5.createVector(0, 100), @@ -89,7 +89,7 @@ suite('p5.Geometry', function() { expect(geom._addJoin).toHaveBeenCalledTimes(4); }); - test('calculateBoundingBox()', function() { + test('calculateBoundingBox()', function () { geom.vertices.push( myp5.createVector(0, 0, 0), myp5.createVector(10, 20, 30), @@ -102,8 +102,7 @@ suite('p5.Geometry', function() { assert.deepEqual(boundingBox.offset.array(), [2.5, 10, 15]); }); - - test('degenerate edge in the middle', function() { + test('degenerate edge in the middle', function () { geom.vertices.push( myp5.createVector(0, 0), myp5.createVector(0, 100), @@ -121,7 +120,7 @@ suite('p5.Geometry', function() { expect(geom._addJoin).toHaveBeenCalledTimes(2); }); - test('degenerate edge at the end', function() { + test('degenerate edge at the end', function () { geom.vertices.push( myp5.createVector(0, 0), myp5.createVector(0, 100), @@ -139,7 +138,7 @@ suite('p5.Geometry', function() { expect(geom._addJoin).toHaveBeenCalledTimes(2); }); - test('degenerate edge between two disconnected polylines', function() { + test('degenerate edge between two disconnected polylines', function () { geom.vertices.push( myp5.createVector(0, 0), myp5.createVector(0, 100), @@ -156,7 +155,7 @@ suite('p5.Geometry', function() { }); }); - suite('buildGeometry', function() { + suite('buildGeometry', function () { const checkLights = () => myp5.lights(); const checkMaterials = () => { myp5.fill('#ffea30'); @@ -198,77 +197,86 @@ suite('p5.Geometry', function() { } } - test('Transforms are applied to models', function() { - assertGeometryRendersMatch(function() { - myp5.push(); - myp5.translate(0, -20); - for (let i = 0; i < 4; i++) { - myp5.box(8); - myp5.translate(0, 40/3); - myp5.rotateY(myp5.PI * 0.2); - } - myp5.pop(); - }, [checkMaterials]); + test('Transforms are applied to models', function () { + assertGeometryRendersMatch( + function () { + myp5.push(); + myp5.translate(0, -20); + for (let i = 0; i < 4; i++) { + myp5.box(8); + myp5.translate(0, 40 / 3); + myp5.rotateY(myp5.PI * 0.2); + } + myp5.pop(); + }, + [checkMaterials] + ); }); - test('Immediate mode constructs are translated correctly', function() { - assertGeometryRendersMatch(function() { - myp5.scale(1/6); - myp5.push(); - myp5.translate(100, -50); - myp5.scale(0.5); - myp5.rotateX(myp5.PI/4); - myp5.cone(); - myp5.pop(); - myp5.cone(); - - myp5.beginShape(); - myp5.bezierOrder(2); - myp5.bezierVertex(-20, -50); - - myp5.bezierVertex(-40, -70); - myp5.bezierVertex(0, -60); - myp5.endShape(); - - myp5.beginShape(myp5.TRIANGLE_STRIP); - for (let y = 20; y <= 60; y += 10) { - for (let x of [20, 60]) { - myp5.vertex(x, y); + test('Immediate mode constructs are translated correctly', function () { + assertGeometryRendersMatch( + function () { + myp5.scale(1 / 6); + myp5.push(); + myp5.translate(100, -50); + myp5.scale(0.5); + myp5.rotateX(myp5.PI / 4); + myp5.cone(); + myp5.pop(); + myp5.cone(); + + myp5.beginShape(); + myp5.bezierOrder(2); + myp5.bezierVertex(-20, -50); + + myp5.bezierVertex(-40, -70); + myp5.bezierVertex(0, -60); + myp5.endShape(); + + myp5.beginShape(myp5.TRIANGLE_STRIP); + for (let y = 20; y <= 60; y += 10) { + for (let x of [20, 60]) { + myp5.vertex(x, y); + } } - } - myp5.endShape(); - - myp5.beginShape(); - myp5.vertex(-100, -120); - myp5.vertex(-120, -110); - myp5.vertex(-105, -100); - myp5.endShape(); - }, [checkLights, checkMaterials, checkNormals]); + myp5.endShape(); + + myp5.beginShape(); + myp5.vertex(-100, -120); + myp5.vertex(-120, -110); + myp5.vertex(-105, -100); + myp5.endShape(); + }, + [checkLights, checkMaterials, checkNormals] + ); }); - test('Vertex colors are captured', function() { - assertGeometryRendersMatch(function() { - myp5.push(); - myp5.translate(0, -10); - myp5.fill('red'); - myp5.sphere(5, 10, 5); - myp5.pop(); - - myp5.push(); - myp5.translate(-10, 10); - myp5.fill('lime'); - myp5.sphere(5, 10, 5); - myp5.pop(); - - myp5.push(); - myp5.translate(10, 10); - myp5.fill('blue'); - myp5.sphere(5, 10, 5); - myp5.pop(); - }, [checkLights]); + test('Vertex colors are captured', function () { + assertGeometryRendersMatch( + function () { + myp5.push(); + myp5.translate(0, -10); + myp5.fill('red'); + myp5.sphere(5, 10, 5); + myp5.pop(); + + myp5.push(); + myp5.translate(-10, 10); + myp5.fill('lime'); + myp5.sphere(5, 10, 5); + myp5.pop(); + + myp5.push(); + myp5.translate(10, 10); + myp5.fill('blue'); + myp5.sphere(5, 10, 5); + myp5.pop(); + }, + [checkLights] + ); }); - test('freeGeometry() cleans up resources', function() { + test('freeGeometry() cleans up resources', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.pixelDensity(1); diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 69ccf620da..23da971909 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -3,29 +3,29 @@ import p5 from '../../../src/app.js'; import '../../js/chai_helpers'; const toArray = typedArray => Array.from(typedArray); -suite('p5.RendererGL', function() { +suite('p5.RendererGL', function () { var myp5; - beforeEach(function() { - myp5 = new p5(function(p) { - p.setup = function() {}; - p.draw = function() {}; + beforeEach(function () { + myp5 = new p5(function (p) { + p.setup = function () {}; + p.draw = function () {}; }); }); - afterEach(function() { + afterEach(function () { myp5.remove(); }); - suite('createCanvas(w, h, WEBGL)', function() { - test('creates a p5.RendererGL renderer', function() { + suite('createCanvas(w, h, WEBGL)', function () { + test('creates a p5.RendererGL renderer', function () { myp5.createCanvas(100, 100, myp5.WEBGL); assert.instanceOf(myp5._renderer, p5.RendererGL); }); }); - suite('noSmooth() canvas position preservation', function() { - test('should maintain the canvas position after calling noSmooth()', function() { + suite('noSmooth() canvas position preservation', function () { + test('should maintain the canvas position after calling noSmooth()', function () { myp5.createCanvas(300, 300, myp5.WEBGL); let cnv = myp5.canvas; cnv.style.position = 'absolute'; @@ -41,21 +41,20 @@ suite('p5.RendererGL', function() { }); }); - - suite('webglVersion', function() { - test('should return WEBGL2 by default', function() { + suite('webglVersion', function () { + test('should return WEBGL2 by default', function () { myp5.createCanvas(10, 10, myp5.WEBGL); assert.equal(myp5.webglVersion, myp5.WEBGL2); }); - test('should return WEBGL1 after setAttributes', function() { + test('should return WEBGL1 after setAttributes', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.setAttributes({ version: 1 }); assert.equal(myp5.webglVersion, myp5.WEBGL); }); // NOTE: should graphics always create WebGL2 canvas? - test.skip('works on p5.Graphics', function() { + test.skip('works on p5.Graphics', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.setAttributes({ version: 1 }); const g = myp5.createGraphics(10, 10, myp5.WEBGL); @@ -63,12 +62,12 @@ suite('p5.RendererGL', function() { assert.equal(g.webglVersion, myp5.WEBGL2); }); - suite('when WebGL2 is unavailable', function() { + suite('when WebGL2 is unavailable', function () { let prevGetContext; - beforeAll(function() { + beforeAll(function () { prevGetContext = HTMLCanvasElement.prototype.getContext; // Mock WebGL2 being unavailable - HTMLCanvasElement.prototype.getContext = function(type, attrs) { + HTMLCanvasElement.prototype.getContext = function (type, attrs) { if (type === 'webgl2') { return undefined; } else { @@ -77,31 +76,34 @@ suite('p5.RendererGL', function() { }; }); - afterAll(function() { + afterAll(function () { // Put back the actual implementation HTMLCanvasElement.prototype.getContext = prevGetContext; }); - test('should return WEBGL1', function() { + test('should return WEBGL1', function () { myp5.createCanvas(10, 10, myp5.WEBGL); assert.equal(myp5.webglVersion, myp5.WEBGL); }); }); }); - suite('p5.strands', function() { - test('a uniform whose name matches a hook parameter name does not break', function() { + suite('p5.strands', function () { + test('a uniform whose name matches a hook parameter name does not break', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.pixelDensity(1); // 'color' is the GLSL parameter name of the getFinalColor hook's first argument. // Creating a uniform with the same name used to cause a GLSL name clash. - const myShader = myp5.baseColorShader().modify(() => { - const color = myp5.uniformFloat('color', 0.5); - myp5.finalColor.begin(); - myp5.finalColor.set([color, color, color, 1]); - myp5.finalColor.end(); - }, { myp5 }); + const myShader = myp5.baseColorShader().modify( + () => { + const color = myp5.uniformFloat('color', 0.5); + myp5.finalColor.begin(); + myp5.finalColor.set([color, color, color, 1]); + myp5.finalColor.end(); + }, + { myp5 } + ); myp5.background(0); myp5.noStroke(); @@ -116,8 +118,8 @@ suite('p5.RendererGL', function() { }); }); - suite('texture binding', function() { - test('setting a custom texture works', function() { + suite('texture binding', function () { + test('setting a custom texture works', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.background(255); @@ -136,7 +138,7 @@ suite('p5.RendererGL', function() { tex.draw(() => myp5.background('red')); myp5.shader(myShader); - myp5.fill('blue') + myp5.fill('blue'); myp5.noStroke(); myShader.setUniform('myTex', tex); @@ -147,7 +149,7 @@ suite('p5.RendererGL', function() { assert.deepEqual(myp5.get(5, 5), [255, 0, 0, 255]); }); - test('textures remain bound after each draw call', function() { + test('textures remain bound after each draw call', function () { myp5.createCanvas(20, 10, myp5.WEBGL); myp5.background(255); @@ -169,7 +171,7 @@ suite('p5.RendererGL', function() { myp5.noStroke(); myShader.setUniform('myTex', tex); - myp5.translate(-myp5.width/2, -myp5.height/2); + myp5.translate(-myp5.width / 2, -myp5.height / 2); myp5.rectMode(myp5.CORNER); // Draw once to the left @@ -183,7 +185,7 @@ suite('p5.RendererGL', function() { assert.deepEqual(myp5.get(15, 5), [255, 0, 0, 255]); }); - test('texture() does not remain bound', function() { + test('texture() does not remain bound', function () { myp5.createCanvas(20, 10, myp5.WEBGL); myp5.background(255); @@ -209,7 +211,7 @@ suite('p5.RendererGL', function() { myp5.pop(); }); - test('user-set uSampler on custom shader is not overridden', function() { + test('user-set uSampler on custom shader is not overridden', function () { myp5.createCanvas(10, 10, myp5.WEBGL); const myShader = myp5.createFilterShader(`precision highp float; @@ -233,8 +235,8 @@ void main() { }); }); - suite('default stroke shader', function() { - test('check activate and deactivating fill and stroke', function() { + suite('default stroke shader', function () { + test('check activate and deactivating fill and stroke', function () { myp5.noStroke(); assert( !myp5._renderer.states.strokeColor, @@ -256,15 +258,15 @@ void main() { ); }); - test('coplanar strokes match 2D', function() { - const getColors = function(mode) { + test('coplanar strokes match 2D', function () { + const getColors = function (mode) { myp5.createCanvas(20, 20, mode); myp5.pixelDensity(1); myp5.background(255); myp5.strokeCap(myp5.SQUARE); myp5.strokeJoin(myp5.MITER); if (mode === myp5.WEBGL) { - myp5.translate(-myp5.width/2, -myp5.height/2); + myp5.translate(-myp5.width / 2, -myp5.height / 2); } myp5.stroke('black'); myp5.strokeWeight(4); @@ -289,11 +291,11 @@ void main() { }); }); - suite('filter shader', function() { + suite('filter shader', function () { let frag; let notAllBlack; - beforeAll(function() { + beforeAll(function () { frag = `precision highp float; varying vec2 vTexCoord; @@ -315,9 +317,11 @@ void main() { // black/white canvas could be an indicator of failed shader logic let val = invert ? 255 : 0; for (let i = 0; i < pixels.length; i++) { - if (pixels[i] !== val || - pixels[i+1] !== val || - pixels[i+2] !== val) { + if ( + pixels[i] !== val || + pixels[i + 1] !== val || + pixels[i + 2] !== val + ) { return true; } } @@ -325,7 +329,7 @@ void main() { }; }); - suite('custom shaders', function() { + suite('custom shaders', function () { function testFilterShader(target) { const fragSrc = `precision highp float; void main() { @@ -335,49 +339,49 @@ void main() { target.filter(s); target.loadPixels(); assert.deepEqual( - target.get(target.width/2, target.height/2), + target.get(target.width / 2, target.height / 2), [255, 255, 0, 255] ); } - test('work with a 2D main canvas', function() { + test('work with a 2D main canvas', function () { myp5.createCanvas(10, 10); testFilterShader(myp5); }); - test('work with a WebGL main canvas', function() { + test('work with a WebGL main canvas', function () { myp5.createCanvas(10, 10, myp5.WEBGL); testFilterShader(myp5); }); - test('work with a 2D graphic', function() { + test('work with a 2D graphic', function () { myp5.createCanvas(10, 10); const graphic = myp5.createGraphics(10, 10); testFilterShader(graphic); }); - test('work with a WebGL graphic', function() { + test('work with a WebGL graphic', function () { myp5.createCanvas(10, 10); const graphic = myp5.createGraphics(10, 10, myp5.WEBGL); testFilterShader(graphic); }); }); - test('filter accepts correct params', function() { + test('filter accepts correct params', function () { myp5.createCanvas(5, 5, myp5.WEBGL); let s = myp5.createFilterShader(frag); myp5.filter(s); myp5.filter(myp5.POSTERIZE, 64); }); - test('secondary graphics layer is instantiated', function() { + test('secondary graphics layer is instantiated', function () { let renderer = myp5.createCanvas(5, 5, myp5.WEBGL); let s = myp5.createFilterShader(frag); myp5.filter(s); assert.notStrictEqual(renderer.filterLayer, undefined); }); - test('custom shader makes changes to main canvas', function() { + test('custom shader makes changes to main canvas', function () { myp5.createCanvas(5, 5, myp5.WEBGL); let s = myp5.createFilterShader(frag); myp5.background('RED'); @@ -389,7 +393,7 @@ void main() { assert.notDeepEqual(p1, p2); }); - test('secondary graphics layer matches main canvas size', function() { + test('secondary graphics layer matches main canvas size', function () { let g1 = myp5.createCanvas(5, 5, myp5.WEBGL); let s = myp5.createFilterShader(frag); myp5.filter(s); @@ -409,7 +413,7 @@ void main() { assert.equal(g1.height, 15); }); - test('create graphics is unaffected after filter', function() { + test('create graphics is unaffected after filter', function () { myp5.createCanvas(5, 5, myp5.WEBGL); let pg = myp5.createGraphics(5, 5, myp5.WEBGL); pg.circle(1, 1, 1); @@ -446,7 +450,7 @@ void main() { assert.deepEqual(p1, p2); }); - test('stroke and other settings are unaffected after filter', function() { + test('stroke and other settings are unaffected after filter', function () { let c = myp5.createCanvas(5, 5, myp5.WEBGL); let getShapeAttributes = () => [ c.states.ellipseMode, @@ -465,18 +469,18 @@ void main() { assert.deepEqual(a1, a2); }); - test('geometries added after filter do not have shader applied', function() { + test('geometries added after filter do not have shader applied', function () { myp5.createCanvas(4, 4, myp5.WEBGL); let s = myp5.createFilterShader(frag); myp5.filter(s); myp5.fill('RED'); myp5.noStroke(); - myp5.rect(-2,-2,2,2); + myp5.rect(-2, -2, 2, 2); myp5.loadPixels(); assert.equal(myp5.pixels[0], 255); }); - test('createFilterShader takes a custom frag shader src', function() { + test('createFilterShader takes a custom frag shader src', function () { let testCreateFilterShader = () => { myp5.createCanvas(4, 4, myp5.WEBGL); let s = myp5.createFilterShader(frag); @@ -485,9 +489,9 @@ void main() { assert.doesNotThrow(testCreateFilterShader, 'this should not throw'); }); - test('filter shader works on a p5.Graphics', function() { - myp5.createCanvas(3,3, myp5.WEBGL); - let pg = myp5.createGraphics(3,3, myp5.WEBGL); + test('filter shader works on a p5.Graphics', function () { + myp5.createCanvas(3, 3, myp5.WEBGL); + let pg = myp5.createGraphics(3, 3, myp5.WEBGL); let s = pg.createFilterShader(frag); pg.background('RED'); pg.loadPixels(); @@ -498,9 +502,9 @@ void main() { assert.notDeepEqual(p1, p2); }); - test('POSTERIZE, BLUR, THRESHOLD work without supplied param', function() { + test('POSTERIZE, BLUR, THRESHOLD work without supplied param', function () { let testDefaultParams = () => { - myp5.createCanvas(3,3, myp5.WEBGL); + myp5.createCanvas(3, 3, myp5.WEBGL); myp5.filter(myp5.POSTERIZE); myp5.filter(myp5.BLUR); myp5.filter(myp5.THRESHOLD); @@ -508,9 +512,9 @@ void main() { assert.doesNotThrow(testDefaultParams, 'this should not throw'); }); - test('filters make changes to canvas', function() { - myp5.createCanvas(20,20); - myp5.circle(10,10,12); + test('filters make changes to canvas', function () { + myp5.createCanvas(20, 20); + myp5.circle(10, 10, 12); let operations = [ myp5.BLUR, myp5.THRESHOLD, @@ -529,10 +533,10 @@ void main() { } }); - test('feedback effects can be prevented (ie. clear() works)', function() { - myp5.createCanvas(20,20); + test('feedback effects can be prevented (ie. clear() works)', function () { + myp5.createCanvas(20, 20); let drawAndFilter = () => { - myp5.circle(5,5,8); + myp5.circle(5, 5, 8); myp5.filter(myp5.BLUR); }; let getPixels = () => { @@ -552,7 +556,7 @@ void main() { assert.arrayApproximately(p1, p2, 1); }); - test('createFilterShader() accepts shader fragments in webgl version 2', function() { + test('createFilterShader() accepts shader fragments in webgl version 2', function () { myp5.createCanvas(5, 5, myp5.WEBGL); let s = myp5.createFilterShader(`#version 300 es precision highp float; @@ -570,12 +574,12 @@ void main() { myp5.filter(s); }); - test('BLUR parameters make different output', function() { + test('BLUR parameters make different output', function () { myp5.createCanvas(10, 10, myp5.WEBGL); let startDraw = () => { myp5.clear(); myp5.fill('RED'); - myp5.circle(0,0,8); + myp5.circle(0, 0, 8); }; let getPixels = () => { myp5.loadPixels(); @@ -590,18 +594,18 @@ void main() { startDraw(); myp5.filter(myp5.BLUR, 50); let p3 = getPixels(); - assert.notDeepEqual(p1,p2); - assert.notDeepEqual(p2,p3); + assert.notDeepEqual(p1, p2); + assert.notDeepEqual(p2, p3); }); - test('POSTERIZE parameters make different output', function() { + test('POSTERIZE parameters make different output', function () { myp5.createCanvas(10, 10, myp5.WEBGL); let startDraw = () => { myp5.clear(); myp5.fill('CORAL'); - myp5.circle(0,0,8); + myp5.circle(0, 0, 8); myp5.fill('CORNFLOWERBLUE'); - myp5.circle(2,2,8); + myp5.circle(2, 2, 8); }; let getPixels = () => { myp5.loadPixels(); @@ -613,15 +617,15 @@ void main() { startDraw(); myp5.filter(myp5.POSTERIZE, 4); let p2 = getPixels(); - assert.notDeepEqual(p1,p2); + assert.notDeepEqual(p1, p2); }); - test('THRESHOLD parameters make different output', function() { + test('THRESHOLD parameters make different output', function () { myp5.createCanvas(10, 10, myp5.WEBGL); let startDraw = () => { myp5.clear(); myp5.fill('RED'); - myp5.circle(0,0,8); + myp5.circle(0, 0, 8); }; let getPixels = () => { myp5.loadPixels(); @@ -633,10 +637,10 @@ void main() { startDraw(); myp5.filter(myp5.THRESHOLD, 0.9); let p2 = getPixels(); - assert.notDeepEqual(p1,p2); + assert.notDeepEqual(p1, p2); }); - suite('external context', function() { + suite('external context', function () { const cases = [ ['no modification', () => {}], ['corner rectMode', () => myp5.rectMode(myp5.CORNER)], @@ -670,21 +674,21 @@ void main() { }; for (const filterType of ['blur', 'invert']) { - suite(`${filterType} filter`, function() { + suite(`${filterType} filter`, function () { for (const mode of ['p2d', 'webgl']) { - suite(`${mode} mode`, function() { + suite(`${mode} mode`, function () { let defaultPixels; beforeEach(() => { - defaultPixels = getFilteredPixels( - 'p2d', - () => {}, filterType - ); + defaultPixels = getFilteredPixels('p2d', () => {}, filterType); }); for (const [name, initialize] of cases) { - test(name, function() { - const pixels = - getFilteredPixels(mode, initialize, filterType); + test(name, function () { + const pixels = getFilteredPixels( + mode, + initialize, + filterType + ); assert.deepEqual(pixels, defaultPixels); }); } @@ -695,15 +699,15 @@ void main() { }); }); - test('contours match 2D', function() { - const getColors = function(mode) { + test('contours match 2D', function () { + const getColors = function (mode) { myp5.createCanvas(50, 50, mode); myp5.pixelDensity(1); myp5.background(200); myp5.strokeCap(myp5.SQUARE); myp5.strokeJoin(myp5.MITER); if (mode === myp5.WEBGL) { - myp5.translate(-myp5.width/2, -myp5.height/2); + myp5.translate(-myp5.width / 2, -myp5.height / 2); } myp5.stroke('black'); myp5.strokeWeight(2); @@ -737,13 +741,15 @@ void main() { } } if (!ok) { - throw new Error(`Expected match:\n\n2D: ${colors2D.img}\n\nWebGL: ${colorsGL.img}`); + throw new Error( + `Expected match:\n\n2D: ${colors2D.img}\n\nWebGL: ${colorsGL.img}` + ); } }); - suite('text shader', function() { - test.todo('rendering looks the same in WebGL1 and 2', function() { - myp5.loadFont('/test/unit/assets/Inconsolata-Bold.ttf', function(font) { + suite('text shader', function () { + test.todo('rendering looks the same in WebGL1 and 2', function () { + myp5.loadFont('/test/unit/assets/Inconsolata-Bold.ttf', function (font) { const webgl2 = myp5.createGraphics(100, 20, myp5.WEBGL); const webgl1 = myp5.createGraphics(100, 20, myp5.WEBGL); webgl1.setAttributes({ version: 1 }); // no longer exists ? @@ -768,8 +774,8 @@ void main() { }); }); - suite('push() and pop() work in WEBGL Mode', function() { - test('push/pop and translation works as expected in WEBGL Mode', function() { + suite('push() and pop() work in WEBGL Mode', function () { + test('push/pop and translation works as expected in WEBGL Mode', function () { myp5.createCanvas(100, 100, myp5.WEBGL); var modelMatrixBefore = myp5._renderer.states.uModelMatrix.copy(); var viewMatrixBefore = myp5._renderer.states.uViewMatrix.copy(); @@ -781,22 +787,28 @@ void main() { myp5.rotateX(Math.random(0, 100)); myp5.translate(20, 100, 5); // Check if the model matrix has changed - assert.notDeepEqual(modelMatrixBefore.mat4, - myp5._renderer.states.uModelMatrix.mat4); + assert.notDeepEqual( + modelMatrixBefore.mat4, + myp5._renderer.states.uModelMatrix.mat4 + ); // Check if the view matrix has changed - assert.notDeepEqual(viewMatrixBefore.mat4, - myp5._renderer.states.uViewMatrix.mat4); + assert.notDeepEqual( + viewMatrixBefore.mat4, + myp5._renderer.states.uViewMatrix.mat4 + ); myp5.pop(); // Check if both the model and view matrices are restored after popping - assert.deepEqual(modelMatrixBefore.mat4, - myp5._renderer.states.uModelMatrix.mat4); + assert.deepEqual( + modelMatrixBefore.mat4, + myp5._renderer.states.uModelMatrix.mat4 + ); assert.deepEqual( viewMatrixBefore.mat4, myp5._renderer.states.uViewMatrix.mat4 ); }); - test('push/pop and directionalLight() works', function() { + test('push/pop and directionalLight() works', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.directionalLight(255, 0, 0, 0, 0, 0); var dirDiffuseColors = @@ -834,7 +846,7 @@ void main() { ); }); - test('push/pop and ambientLight() works', function() { + test('push/pop and ambientLight() works', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.ambientLight(100, 0, 100); myp5.ambientLight(0, 0, 200); @@ -846,7 +858,7 @@ void main() { assert.deepEqual(ambColors, myp5._renderer.states.ambientLightColors); }); - test('push/pop and pointLight() works', function() { + test('push/pop and pointLight() works', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.pointLight(255, 0, 0, 0, 0, 0); var pointDiffuseColors = @@ -877,7 +889,7 @@ void main() { assert.deepEqual(pointLocs, myp5._renderer.states.pointLightPositions); }); - test('push/pop and specularColor() works', function() { + test('push/pop and specularColor() works', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.specularColor(255, 0, 0); var specularColors = myp5._renderer.states.specularColors.slice(); @@ -888,7 +900,7 @@ void main() { assert.deepEqual(specularColors, myp5._renderer.states.specularColors); }); - test('push/pop and spotLight() works', function() { + test('push/pop and spotLight() works', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.spotLight(255, 0, 255, 1, 2, 3, 0, 1, 0, Math.PI / 4, 7); let spotLightDiffuseColors = @@ -941,7 +953,7 @@ void main() { assert.deepEqual(spotLightConc, myp5._renderer.states.spotLightConc); }); - test('push/pop and noLights() works', function() { + test('push/pop and noLights() works', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.ambientLight(0, 0, 200); var ambColors = myp5._renderer.states.ambientLightColors.slice(); @@ -955,7 +967,7 @@ void main() { assert.deepEqual(ambColors, myp5._renderer.states.ambientLightColors); }); - test('push/pop and texture() works', function() { + test('push/pop and texture() works', function () { myp5.createCanvas(100, 100, myp5.WEBGL); var tex1 = myp5.createGraphics(1, 1); myp5.texture(tex1); @@ -976,22 +988,23 @@ void main() { myp5.metalness(100000); myp5.sphere(50); expect(myp5._renderer.mixedAmbientLight).to.not.deep.equal( - myp5._renderer.states.ambientLightColors); + myp5._renderer.states.ambientLightColors + ); }); - test('specularColor transforms to fill color when metalness is applied', - function () { - myp5.createCanvas(100, 100, myp5.WEBGL); - myp5.fill(0, 0, 0, 0); - myp5.specularMaterial(255, 255, 255, 255); - myp5.noStroke(); - myp5.metalness(100000); - myp5.sphere(50); - expect(myp5._renderer.mixedSpecularColor).to.deep.equal( - myp5._renderer.states.curFillColor); - }); + test('specularColor transforms to fill color when metalness is applied', function () { + myp5.createCanvas(100, 100, myp5.WEBGL); + myp5.fill(0, 0, 0, 0); + myp5.specularMaterial(255, 255, 255, 255); + myp5.noStroke(); + myp5.metalness(100000); + myp5.sphere(50); + expect(myp5._renderer.mixedSpecularColor).to.deep.equal( + myp5._renderer.states.curFillColor + ); + }); - test('push/pop and shader() works with fill shaders by default', function() { + test('push/pop and shader() works with fill shaders by default', function () { myp5.createCanvas(100, 100, myp5.WEBGL); var fillShader1 = myp5._renderer._getLightShader(); var fillShader2 = myp5._renderer._getColorShader(); @@ -1005,7 +1018,7 @@ void main() { assert.equal(fillShader1, myp5._renderer.states.userFillShader); }); - test('push/pop builds/unbuilds stack properly', function() { + test('push/pop builds/unbuilds stack properly', function () { myp5.createCanvas(100, 100, myp5.WEBGL); var col1 = myp5.color(255, 0, 0); var col2 = myp5.color(0, 255, 0); @@ -1029,8 +1042,8 @@ void main() { }); }); - suite('applying cameras', function() { - test('changing cameras keeps transforms', function() { + suite('applying cameras', function () { + test('changing cameras keeps transforms', function () { myp5.createCanvas(50, 50, myp5.WEBGL); const origModelMatrix = myp5._renderer.states.uModelMatrix.copy(); @@ -1072,8 +1085,8 @@ void main() { }); }); - suite('materials', function() { - test('ambient color defaults to the fill color', function() { + suite('materials', function () { + test('ambient color defaults to the fill color', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.noStroke(); myp5.lights(); @@ -1085,7 +1098,7 @@ void main() { expect(pixel[2]).to.equal(0); }); - test('ambient color can be set manually', function() { + test('ambient color can be set manually', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.noStroke(); myp5.lights(); @@ -1098,7 +1111,7 @@ void main() { expect(pixel[2]).to.equal(128); }); - test('specular is not lost by texture()', function() { + test('specular is not lost by texture()', function () { myp5.createCanvas(100, 100, myp5.WEBGL); const tex = myp5.createGraphics(100, 100); tex.background(64); @@ -1113,7 +1126,7 @@ void main() { assert.deepEqual(pixel, [184, 184, 184, 255]); }); - test('specularMaterial() does not kill texture', function() { + test('specularMaterial() does not kill texture', function () { myp5.createCanvas(100, 100, myp5.WEBGL); const tex = myp5.createGraphics(100, 100); tex.background(64); @@ -1128,7 +1141,7 @@ void main() { assert.deepEqual(pixel, [184, 184, 184, 255]); }); - test('ambientMaterial() does not kill texture', function() { + test('ambientMaterial() does not kill texture', function () { myp5.createCanvas(100, 100, myp5.WEBGL); const tex = myp5.createGraphics(100, 100); tex.background(64); @@ -1143,7 +1156,7 @@ void main() { assert.deepEqual(pixel, [88, 88, 88, 255]); }); - test('emissiveMaterial() does not kill texture', function() { + test('emissiveMaterial() does not kill texture', function () { myp5.createCanvas(100, 100, myp5.WEBGL); const tex = myp5.createGraphics(100, 100); tex.background(64); @@ -1159,8 +1172,8 @@ void main() { }); }); - suite('loadpixels()', function() { - test('loadPixels color check', function() { + suite('loadpixels()', function () { + test('loadPixels color check', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.background(0, 100, 0); myp5.loadPixels(); @@ -1169,7 +1182,7 @@ void main() { assert.deepEqual(pixels[3], 255); }); - test('get() singlePixel color and size, with loadPixels', function() { + test('get() singlePixel color and size, with loadPixels', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.background(100, 115, 100); myp5.loadPixels(); @@ -1178,10 +1191,10 @@ void main() { assert.isTrue(img.length === 4); }); - test('updatePixels() matches 2D mode', function() { + test('updatePixels() matches 2D mode', function () { myp5.createCanvas(20, 20); myp5.pixelDensity(1); - const getColors = function(mode) { + const getColors = function (mode) { const g = myp5.createGraphics(20, 20, mode); g.pixelDensity(1); g.background(255); @@ -1215,20 +1228,20 @@ void main() { }); }); - suite('get()', function() { + suite('get()', function () { var img; - test('get() size check', function() { + test('get() size check', function () { myp5.createCanvas(100, 100, myp5.WEBGL); img = myp5.get(); assert.deepEqual(img.width, myp5.width); }); - test('get() can create p5.Image', function() { + test('get() can create p5.Image', function () { myp5.createCanvas(100, 100, myp5.WEBGL); assert.isTrue(img instanceof p5.Image); }); - test('get() singlePixel color and size', function() { + test('get() singlePixel color and size', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.background(100, 115, 100); img = myp5.get(0, 0); @@ -1241,10 +1254,10 @@ void main() { }); }); - suite('GL Renderer clear()', function() { + suite('GL Renderer clear()', function () { var pg; var pixel; - test('webgl graphics background draws into webgl canvas', function() { + test('webgl graphics background draws into webgl canvas', function () { myp5.createCanvas(50, 50, myp5.WEBGL); myp5.background(0, 255, 255, 255); pg = myp5.createGraphics(25, 50, myp5.WEBGL); @@ -1254,7 +1267,7 @@ void main() { assert.deepEqual(pixel, [0, 0, 0, 255]); }); - test('transparent GL graphics with GL canvas', function() { + test('transparent GL graphics with GL canvas', function () { myp5.createCanvas(50, 50, myp5.WEBGL); pg = myp5.createGraphics(25, 50, myp5.WEBGL); myp5.background(0, 255, 255); @@ -1264,7 +1277,7 @@ void main() { assert.deepEqual(pixel, [0, 255, 255, 255]); }); - test('clear color with rgba arguments', function() { + test('clear color with rgba arguments', function () { myp5.createCanvas(50, 50, myp5.WEBGL); myp5.clear(1, 0, 0, 1); pixel = myp5.get(0, 0); @@ -1275,7 +1288,7 @@ void main() { assert.deepEqual(pixel, [255, 0, 0, 255]); }); - test('semi-transparent GL graphics with GL canvas', function() { + test('semi-transparent GL graphics with GL canvas', function () { myp5.createCanvas(50, 50, myp5.WEBGL); pg = myp5.createGraphics(25, 50, myp5.WEBGL); myp5.background(0, 255, 255); @@ -1285,7 +1298,7 @@ void main() { assert.deepEqual(pixel, [39, 194, 194, 255]); }); - test('webgl graphics background draws into 2D canvas', function() { + test('webgl graphics background draws into 2D canvas', function () { myp5.createCanvas(50, 50); myp5.background(0, 255, 255, 255); pg = myp5.createGraphics(25, 50, myp5.WEBGL); @@ -1295,7 +1308,7 @@ void main() { assert.deepEqual(pixel, [0, 0, 0, 255]); }); - test('transparent GL graphics with 2D canvas', function() { + test('transparent GL graphics with 2D canvas', function () { myp5.createCanvas(50, 50); pg = myp5.createGraphics(25, 50, myp5.WEBGL); myp5.background(0, 255, 255); @@ -1305,7 +1318,7 @@ void main() { assert.deepEqual(pixel, [0, 255, 255, 255]); }); - test('semi-transparent GL graphics with 2D canvas', function() { + test('semi-transparent GL graphics with 2D canvas', function () { myp5.createCanvas(50, 50); pg = myp5.createGraphics(25, 50, myp5.WEBGL); myp5.background(0, 255, 255); @@ -1316,14 +1329,14 @@ void main() { }); }); - suite('background()', function() { + suite('background()', function () { function assertAllPixelsAreColor(target, r, g, b, a) { target.loadPixels(); const expectedPixels = []; for (let i = 0; i < target.width * target.height; i++) { expectedPixels.push(r, g, b, a); } - assert.deepEqual([ ...target.pixels ], expectedPixels); + assert.deepEqual([...target.pixels], expectedPixels); } function testDepthGetsCleared(target) { @@ -1345,24 +1358,24 @@ void main() { assertAllPixelsAreColor(target, 0, 0, 255, 255); } - test('background() resets the depth buffer of the main canvas', function() { + test('background() resets the depth buffer of the main canvas', function () { myp5.createCanvas(10, 10, myp5.WEBGL); testDepthGetsCleared(myp5); }); - test('background() resets the depth buffer of p5.Graphics', function() { + test('background() resets the depth buffer of p5.Graphics', function () { const graphics = myp5.createGraphics(10, 10, myp5.WEBGL); testDepthGetsCleared(graphics); }); }); - suite('blendMode()', function() { - var testBlend = function(mode, intended) { + suite('blendMode()', function () { + var testBlend = function (mode, intended) { myp5.blendMode(mode); assert.deepEqual(intended, myp5._renderer.states.curBlendMode); }; - test('blendMode sets _curBlendMode correctly', function() { + test('blendMode sets _curBlendMode correctly', function () { myp5.createCanvas(100, 100, myp5.WEBGL); testBlend(myp5.ADD, myp5.ADD); testBlend(myp5.REPLACE, myp5.REPLACE); @@ -1374,7 +1387,7 @@ void main() { testBlend(myp5.DARKEST, myp5.DARKEST); }); - test('blendMode doesnt change when mode unavailable in 3D', function() { + test('blendMode doesnt change when mode unavailable in 3D', function () { myp5.createCanvas(100, 100, myp5.WEBGL); myp5.blendMode(myp5.DARKEST); testBlend(myp5.BURN, myp5.DARKEST); @@ -1384,7 +1397,7 @@ void main() { testBlend(myp5.OVERLAY, myp5.DARKEST); }); - var mixAndReturn = function(mode, bgCol) { + var mixAndReturn = function (mode, bgCol) { myp5.background(bgCol); myp5.blendMode(mode); myp5.fill(255, 0, 0, 122); @@ -1394,7 +1407,7 @@ void main() { return myp5.get(5, 5); }; - test('blendModes change pixel colors as expected', function() { + test('blendModes change pixel colors as expected', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.noStroke(); assert.deepEqual([122, 0, 122, 255], mixAndReturn(myp5.ADD, 0)); @@ -1409,12 +1422,12 @@ void main() { assert.deepEqual([0, 0, 0, 255], mixAndReturn(myp5.DARKEST, 255)); }); - test('blendModes match 2D mode', function() { + test('blendModes match 2D mode', function () { myp5.createCanvas(10, 10, myp5.WEBGL); const ref = myp5.createGraphics(myp5.width, myp5.height); ref.translate(ref.width / 2, ref.height / 2); // Match WebGL mode - const testBlend = function(target, colorA, colorB, mode) { + const testBlend = function (target, colorA, colorB, mode) { target.clear(); target.push(); target.background(0); @@ -1426,11 +1439,13 @@ void main() { target.fill(colorB); target.rect(0, 0, target.width, target.height); target.pop(); - console.log(`${colorA} ${mode} ${colorB}: ` + target.canvas.toDataURL()); + console.log( + `${colorA} ${mode} ${colorB}: ` + target.canvas.toDataURL() + ); return target.get(0, 0); }; - const assertSameIn2D = function(colorA, colorB, mode) { + const assertSameIn2D = function (colorA, colorB, mode) { const refColor = testBlend(myp5, colorA, colorB, mode); const webglColor = testBlend(ref, colorA, colorB, mode); // console.log(`Blending ${colorA} with ${colorB} using ${mode}: ${JSON.stringify(refColor)}, ${JSON.stringify(webglColor)}`) @@ -1442,7 +1457,7 @@ void main() { ); }; - for (const alpha of [1, 200/255]) { + for (const alpha of [1, 200 / 255]) { const red = myp5.color('#F53'); const blue = myp5.color('#13F'); red.setAlpha(alpha); @@ -1456,12 +1471,16 @@ void main() { } }); - test('blendModes are included in push/pop', function() { + test('blendModes are included in push/pop', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.blendMode(myp5.MULTIPLY); myp5.push(); myp5.blendMode(myp5.ADD); - assert.equal(myp5._renderer.states.curBlendMode, myp5.ADD, 'Changed to ADD'); + assert.equal( + myp5._renderer.states.curBlendMode, + myp5.ADD, + 'Changed to ADD' + ); myp5.pop(); assert.equal( myp5._renderer.states.curBlendMode, @@ -1470,7 +1489,7 @@ void main() { ); }); - test('blendModes are applied to point drawing', function() { + test('blendModes are applied to point drawing', function () { myp5.createCanvas(32, 32, myp5.WEBGL); myp5.background(0); myp5.blendMode(myp5.ADD); @@ -1482,7 +1501,7 @@ void main() { assert.deepEqual(myp5.get(16, 16), [255, 0, 255, 255]); }); - test('transparency works the same with per-vertex colors', function() { + test('transparency works the same with per-vertex colors', function () { myp5.createCanvas(20, 20, myp5.WEBGL); myp5.noStroke(); @@ -1507,8 +1526,8 @@ void main() { }); }); - suite('BufferDef', function() { - test('render buffer properties are correctly set', function() { + suite('BufferDef', function () { + test('render buffer properties are correctly set', function () { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.fill(255); @@ -1535,23 +1554,21 @@ void main() { // + 6 verts per line cap x0 (1 quad each) // = 54 assert.equal(geom.lineVertices.length, 54 * 3); - }); }); - suite('tint() in WEBGL mode', function() { - test('default tint value', function() { + suite('tint() in WEBGL mode', function () { + test('default tint value', function () { myp5.createCanvas(100, 100, myp5.WEBGL); assert.deepEqual( - myp5._renderer.states.tint?._getRGBA([255, 255, 255, 255]) ?? [255, 255, 255, 255], + myp5._renderer.states.tint?._getRGBA([255, 255, 255, 255]) ?? [ + 255, 255, 255, 255 + ], [255, 255, 255, 255] ); }); - - - test('tint value is modified correctly when tint() is called', function() { - + test('tint value is modified correctly when tint() is called', function () { function assertColorEq(tint, colArray) { assert.deepEqual(tint._getRGBA([255, 255, 255, 255]), colArray); } @@ -1592,28 +1609,30 @@ void main() { assertColorEq(myp5._renderer.states.tint, [255, 204, 0, 255]); }); - test('tint should be reset after draw loop', function() { - return new Promise(function(resolve, reject) { - new p5(function(p) { - p.setup = function() { + test('tint should be reset after draw loop', function () { + return new Promise(function (resolve, reject) { + new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100, myp5.WEBGL); }; - p.draw = function() { + p.draw = function () { if (p.frameCount === 2) { resolve(p._renderer.states.tint); } p.tint(0, 153, 204, 126); }; }); - }).then(function(_tint) { - assert.deepEqual(_tint?._getRGBA([255, 255, 255, 255]) ?? [255, 255, 255, 255], - [255, 255, 255, 255]); + }).then(function (_tint) { + assert.deepEqual( + _tint?._getRGBA([255, 255, 255, 255]) ?? [255, 255, 255, 255], + [255, 255, 255, 255] + ); }); }); }); - suite('beginShape() in WEBGL mode', function() { - test('QUADS mode converts into triangles', function() { + suite('beginShape() in WEBGL mode', function () { + test('QUADS mode converts into triangles', function () { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); myp5.beginShape(myp5.QUADS); @@ -1665,7 +1684,7 @@ void main() { renderer.shapeBuilder.geometry.vertices.length, expectedVerts.length ); - expectedVerts.forEach(function([x, y, z], i) { + expectedVerts.forEach(function ([x, y, z], i) { assert.equal(renderer.shapeBuilder.geometry.vertices[i].x, x); assert.equal(renderer.shapeBuilder.geometry.vertices[i].y, y); assert.equal(renderer.shapeBuilder.geometry.vertices[i].z, z); @@ -1733,14 +1752,14 @@ void main() { renderer.shapeBuilder.geometry.vertexNormals.length, expectedNormals.length ); - expectedNormals.forEach(function([x, y, z], i) { + expectedNormals.forEach(function ([x, y, z], i) { assert.equal(renderer.shapeBuilder.geometry.vertexNormals[i].x, x); assert.equal(renderer.shapeBuilder.geometry.vertexNormals[i].y, y); assert.equal(renderer.shapeBuilder.geometry.vertexNormals[i].z, z); }); }); - test('QUADS mode makes edges for quad outlines', function() { + test('QUADS mode makes edges for quad outlines', function () { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.beginShape(myp5.QUADS); myp5.vertex(0, 0); @@ -1757,7 +1776,7 @@ void main() { assert.equal(renderer.shapeBuilder.geometry.edges.length, 8); }); - test('QUAD_STRIP mode makes edges for strip outlines', function() { + test('QUAD_STRIP mode makes edges for strip outlines', function () { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.beginShape(myp5.QUAD_STRIP); myp5.vertex(0, 0); @@ -1774,7 +1793,7 @@ void main() { assert.equal(renderer.shapeBuilder.geometry.edges.length, 10); }); - test('TRIANGLE_FAN mode makes edges for each triangle', function() { + test('TRIANGLE_FAN mode makes edges for each triangle', function () { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); // x // | \ @@ -1792,7 +1811,7 @@ void main() { assert.equal(renderer.shapeBuilder.geometry.edges.length, 7); }); - test('PATH preserves vertex data', function() { + test('PATH preserves vertex data', function () { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); @@ -1867,35 +1886,23 @@ void main() { [1, 1, 1] ); - assert.deepEqual(renderer.shapeBuilder.geometry.aCustomSrc, [ - 1, 0, 0, - 0, 0, 1, - 1, 1, 1, - 0, 0, 1, - 1, 0, 0, - 0, 1, 0 - ]); - - assert.deepEqual(renderer.shapeBuilder.geometry.vertexColors, [ - 1, 0, 0, 1, - 0, 0, 1, 1, - 1, 1, 1, 1, - 0, 0, 1, 1, - 1, 0, 0, 1, - 0, 1, 0, 1 - ]); - - assert.deepEqual(renderer.shapeBuilder.geometry.uvs, [ - 1, 0, - 0, 1, - 0, 0, - 0, 1, - 1, 0, - 1, 1 - ]); - }); - - test('PATH does not affect stroke colors', function() { + assert.deepEqual( + renderer.shapeBuilder.geometry.aCustomSrc, + [1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0] + ); + + assert.deepEqual( + renderer.shapeBuilder.geometry.vertexColors, + [1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1] + ); + + assert.deepEqual( + renderer.shapeBuilder.geometry.uvs, + [1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1] + ); + }); + + test('PATH does not affect stroke colors', function () { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); @@ -1912,16 +1919,13 @@ void main() { myp5.endShape(myp5.CLOSE); // Vertex stroke colors are not run through libtess - assert.deepEqual(renderer.shapeBuilder.geometry.vertexStrokeColors, [ - 1, 1, 1, 1, - 1, 0, 0, 1, - 0, 1, 0, 1, - 0, 0, 1, 1, - 1, 1, 1, 1 - ]); + assert.deepEqual( + renderer.shapeBuilder.geometry.vertexStrokeColors, + [1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1] + ); }); - test('PATH does not affect texture coordinates', function() { + test('PATH does not affect texture coordinates', function () { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); const texture = new p5.Image(25, 25); @@ -1936,18 +1940,17 @@ void main() { myp5.endShape(myp5.CLOSE); // UVs are correctly translated through libtess - assert.deepEqual(renderer.shapeBuilder.geometry.uvs, [ - 1, 0, - 0, 1, - 0, 0, + assert.deepEqual( + renderer.shapeBuilder.geometry.uvs, + [ + 1, 0, 0, 1, 0, 0, - 0, 1, - 1, 0, - 1, 1 - ]); + 0, 1, 1, 0, 1, 1 + ] + ); }); - test('PATH interpolates vertex data at intersections', function() { + test('PATH interpolates vertex data at intersections', function () { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); // Hourglass shape: @@ -2028,26 +2031,21 @@ void main() { [1, -1, 1] ); - assert.deepEqual(renderer.shapeBuilder.geometry.vertexColors, [ - 0.5, 0.5, 0.5, 1, - 0, 0, 1, 1, - 1, 1, 1, 1, - 0, 1, 0, 1, - 0.5, 0.5, 0.5, 1, - 1, 0, 0, 1 - ]); + assert.deepEqual( + renderer.shapeBuilder.geometry.vertexColors, + [ + 0.5, 0.5, 0.5, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0.5, 0.5, 0.5, + 1, 1, 0, 0, 1 + ] + ); - assert.deepEqual(renderer.shapeBuilder.geometry.uvs, [ - 0.5, 0.5, - 0, 1, - 0, 0, - 1, 1, - 0.5, 0.5, - 1, 0 - ]); + assert.deepEqual( + renderer.shapeBuilder.geometry.uvs, + [0.5, 0.5, 0, 1, 0, 0, 1, 1, 0.5, 0.5, 1, 0] + ); }); - test('PATH handles vertex data perpendicular to the camera', function() { + test('PATH handles vertex data perpendicular to the camera', function () { var renderer = myp5.createCanvas(10, 10, myp5.WEBGL); myp5.textureMode(myp5.NORMAL); @@ -2085,14 +2083,13 @@ void main() { ); }); - suite('large tessellation guard', function() { - test('prompts user before tessellating >50k vertices', function() { + suite('large tessellation guard', function () { + test('prompts user before tessellating >50k vertices', function () { const renderer = myp5.createCanvas(10, 10, myp5.WEBGL); const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); - const tessSpy = vi.spyOn( - renderer.shapeBuilder, - '_tesselateShape' - ).mockImplementation(() => {}); + const tessSpy = vi + .spyOn(renderer.shapeBuilder, '_tesselateShape') + .mockImplementation(() => {}); myp5.beginShape(); for (let i = 0; i < 60000; i++) { @@ -2108,13 +2105,12 @@ void main() { tessSpy.mockRestore(); }); - test('only prompts once when user approves large tessellation', function() { + test('only prompts once when user approves large tessellation', function () { const renderer = myp5.createCanvas(10, 10, myp5.WEBGL); const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true); - const tessSpy = vi.spyOn( - renderer.shapeBuilder, - '_tesselateShape' - ).mockImplementation(() => {}); + const tessSpy = vi + .spyOn(renderer.shapeBuilder, '_tesselateShape') + .mockImplementation(() => {}); myp5.beginShape(); for (let i = 0; i < 60000; i++) { @@ -2137,13 +2133,12 @@ void main() { tessSpy.mockRestore(); }); - test('skips prompt when p5.disableFriendlyErrors is true', function() { + test('skips prompt when p5.disableFriendlyErrors is true', function () { const renderer = myp5.createCanvas(10, 10, myp5.WEBGL); const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); - const tessSpy = vi.spyOn( - renderer.shapeBuilder, - '_tesselateShape' - ).mockImplementation(() => {}); + const tessSpy = vi + .spyOn(renderer.shapeBuilder, '_tesselateShape') + .mockImplementation(() => {}); p5.disableFriendlyErrors = true; myp5.beginShape(); @@ -2160,7 +2155,7 @@ void main() { tessSpy.mockRestore(); }); - test('works normally for <50k vertices', function() { + test('works normally for <50k vertices', function () { const renderer = myp5.createCanvas(10, 10, myp5.WEBGL); const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); @@ -2178,8 +2173,8 @@ void main() { }); }); - suite('color interpolation', function() { - test('strokes should interpolate colors between vertices', function() { + suite('color interpolation', function () { + test('strokes should interpolate colors between vertices', function () { const renderer = myp5.createCanvas(512, 4, myp5.WEBGL); // far left color: (242, 236, 40) @@ -2199,7 +2194,7 @@ void main() { assert.deepEqual(myp5.get(511, 2), [42, 36, 240, 255]); }); - test('geometry without stroke colors use curStrokeColor', function() { + test('geometry without stroke colors use curStrokeColor', function () { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); myp5.background(255); myp5.fill(255); @@ -2212,28 +2207,45 @@ void main() { assert.deepEqual(myp5.get(128, 0), [0, 0, 0, 255]); }); - test('geometry with stroke colors use their colors', function() { + test('geometry with stroke colors use their colors', function () { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); - const myGeom = new p5.Geometry(1, 1, function() { - this.gid = 'strokeColorTest'; - this.vertices.push(myp5.createVector(-128, -128)); - this.vertices.push(myp5.createVector(-128, 128)); - this.vertices.push(myp5.createVector(128, 128)); - this.vertices.push(myp5.createVector(128, -128)); - this.faces.push([0, 1, 2]); - this.faces.push([0, 2, 3]); - this.edges.push([0, 1]); - this.edges.push([1, 2]); - this.edges.push([2, 3]); - this.edges.push([3, 0]); - this.vertexStrokeColors.push( - 0, 0, 0, 1, - 1, 0, 0, 1, - 0, 0, 1, 1, - 0, 1, 0, 1 - ); - this._edgesToVertices(); - }, myp5._renderer); + const myGeom = new p5.Geometry( + 1, + 1, + function () { + this.gid = 'strokeColorTest'; + this.vertices.push(myp5.createVector(-128, -128)); + this.vertices.push(myp5.createVector(-128, 128)); + this.vertices.push(myp5.createVector(128, 128)); + this.vertices.push(myp5.createVector(128, -128)); + this.faces.push([0, 1, 2]); + this.faces.push([0, 2, 3]); + this.edges.push([0, 1]); + this.edges.push([1, 2]); + this.edges.push([2, 3]); + this.edges.push([3, 0]); + this.vertexStrokeColors.push( + 0, + 0, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 1 + ); + this._edgesToVertices(); + }, + myp5._renderer + ); myp5.background(255); myp5.fill(255); myp5.strokeWeight(4); @@ -2244,7 +2256,7 @@ void main() { assert.deepEqual(myp5.get(128, 255), [127, 0, 128, 255]); }); - test('immediate mode uses stroke colors', function() { + test('immediate mode uses stroke colors', function () { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); myp5.background(255); myp5.fill(255); @@ -2265,8 +2277,8 @@ void main() { }); }); - suite('interpolation of vertex colors', function(){ - test('immediate mode uses vertex colors (noLight)', function() { + suite('interpolation of vertex colors', function () { + test('immediate mode uses vertex colors (noLight)', function () { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); // upper color: (200, 0, 0, 255); @@ -2288,7 +2300,7 @@ void main() { assert.deepEqual(myp5.get(128, 128), [100, 0, 100, 255]); }); - test('immediate mode uses vertex colors (light)', function() { + test('immediate mode uses vertex colors (light)', function () { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); myp5.directionalLight(255, 255, 255, 0, 0, -1); @@ -2310,7 +2322,7 @@ void main() { assert.deepEqual(myp5.get(128, 128), [73, 0, 73, 255]); }); - test('geom without vertex colors use curFillCol (noLight)', function() { + test('geom without vertex colors use curFillCol (noLight)', function () { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); // expected center color is curFillColor. @@ -2323,7 +2335,7 @@ void main() { assert.deepEqual(myp5.get(128, 128), [200, 0, 200, 255]); }); - test('geom without vertex colors use curFillCol (light)', function() { + test('geom without vertex colors use curFillCol (light)', function () { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); myp5.directionalLight(255, 255, 255, 0, 0, -1); @@ -2338,14 +2350,14 @@ void main() { assert.deepEqual(myp5.get(128, 128), [146, 0, 146, 255]); }); - test('geom with vertex colors use their color (noLight)', function() { + test('geom with vertex colors use their color (noLight)', function () { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); // upper color: (200, 0, 0, 255); // lower color: (0, 0, 200, 255); // expected center color: (100, 0, 100, 255); - const myGeom = new p5.Geometry(1, 1, function() { + const myGeom = new p5.Geometry(1, 1, function () { this.gid = 'vertexColorTestWithNoLights'; this.vertices.push(myp5.createVector(-128, -128)); this.vertices.push(myp5.createVector(128, -128)); @@ -2354,10 +2366,22 @@ void main() { this.faces.push([0, 1, 2]); this.faces.push([0, 2, 3]); this.vertexColors.push( - 200/255, 0, 0, 1, - 200/255, 0, 0, 1, - 0, 0, 200/255, 1, - 0, 0, 200/255, 1 + 200 / 255, + 0, + 0, + 1, + 200 / 255, + 0, + 0, + 1, + 0, + 0, + 200 / 255, + 1, + 0, + 0, + 200 / 255, + 1 ); this.computeNormals(); }); @@ -2369,10 +2393,10 @@ void main() { assert.deepEqual(myp5.get(128, 128), [100, 0, 100, 255]); }); - test('geom with vertex colors use their color (light)', function() { + test('geom with vertex colors use their color (light)', function () { const renderer = myp5.createCanvas(256, 256, myp5.WEBGL); - const myGeom = new p5.Geometry(1, 1, function() { + const myGeom = new p5.Geometry(1, 1, function () { this.gid = 'vertexColorTestWithLighs'; this.vertices.push(myp5.createVector(-128, -128)); this.vertices.push(myp5.createVector(128, -128)); @@ -2381,10 +2405,22 @@ void main() { this.faces.push([0, 1, 2]); this.faces.push([0, 2, 3]); this.vertexColors.push( - 200/255, 0, 0, 1, - 200/255, 0, 0, 1, - 0, 0, 200/255, 1, - 0, 0, 200/255, 1 + 200 / 255, + 0, + 0, + 1, + 200 / 255, + 0, + 0, + 1, + 0, + 0, + 200 / 255, + 1, + 0, + 0, + 200 / 255, + 1 ); this.computeNormals(); }); @@ -2400,21 +2436,26 @@ void main() { }); }); - suite('Test for register availability', function() { - test('register enable/disable flag test', function() { + suite('Test for register availability', function () { + test('register enable/disable flag test', function () { const renderer = myp5.createCanvas(16, 16, myp5.WEBGL); // geometry without aTexCoord. - const myGeom = new p5.Geometry(1, 1, function() { - this.gid = 'registerEnabledTest'; - this.vertices.push(myp5.createVector(-8, -8)); - this.vertices.push(myp5.createVector(8, -8)); - this.vertices.push(myp5.createVector(8, 8)); - this.vertices.push(myp5.createVector(-8, 8)); - this.faces.push([0, 1, 2]); - this.faces.push([0, 2, 3]); - this.computeNormals(); - }, myp5._renderer); + const myGeom = new p5.Geometry( + 1, + 1, + function () { + this.gid = 'registerEnabledTest'; + this.vertices.push(myp5.createVector(-8, -8)); + this.vertices.push(myp5.createVector(8, -8)); + this.vertices.push(myp5.createVector(8, 8)); + this.vertices.push(myp5.createVector(-8, 8)); + this.faces.push([0, 1, 2]); + this.faces.push([0, 2, 3]); + this.computeNormals(); + }, + myp5._renderer + ); myp5.fill(255); myp5.noStroke(); @@ -2437,8 +2478,8 @@ void main() { }); }); - suite('setAttributes', function() { - test('It leaves a reference to the correct canvas', function() { + suite('setAttributes', function () { + test('It leaves a reference to the correct canvas', function () { const renderer = myp5.createCanvas(10, 10, myp5.WEBGL); assert.equal(myp5.canvas, renderer.canvas); @@ -2447,8 +2488,8 @@ void main() { }); }); - suite('instancing', function() { - test('instanced', function() { + suite('instancing', function () { + test('instanced', function () { let defShader; const vertShader = `#version 300 es @@ -2495,14 +2536,8 @@ void main() { myp5.shader(defShader); { // Check to make sure that pixels are empty first - assert.deepEqual( - myp5.get(0, 0), - [0, 0, 0, 255] - ); - assert.deepEqual( - myp5.get(1, 0), - [0, 0, 0, 255] - ); + assert.deepEqual(myp5.get(0, 0), [0, 0, 0, 255]); + assert.deepEqual(myp5.get(1, 0), [0, 0, 0, 255]); const siz = 1; myp5.translate(-myp5.width / 2, -myp5.height / 2); @@ -2514,28 +2549,22 @@ void main() { myp5.endShape(myp5.CLOSE, 2); // check the pixels after instancing to make sure that they're the correct color - assert.deepEqual( - myp5.get(0, 0), - [255, 0, 0, 255] - ); - assert.deepEqual( - myp5.get(1, 0), - [255, 0, 0, 255] - ); + assert.deepEqual(myp5.get(0, 0), [255, 0, 0, 255]); + assert.deepEqual(myp5.get(1, 0), [255, 0, 0, 255]); } myp5.resetShader(); }); }); - suite('clip()', function() { + suite('clip()', function () { //let myp5; function getClippedPixels(mode, mask) { // Clean up the last myp5 instance, since this may be called more than // once per test myp5.remove(); - myp5 = new p5(function(p) { - p.setup = function() {}; - p.draw = function() {}; + myp5 = new p5(function (p) { + p.setup = function () {}; + p.draw = function () {}; }); myp5.createCanvas(50, 50, mode); @@ -2553,7 +2582,7 @@ void main() { return pixels.slice(start, start + 4); } - test('It can mask in a shape', function() { + test('It can mask in a shape', function () { const mask = () => { myp5.background(255); myp5.noStroke(); @@ -2577,7 +2606,7 @@ void main() { assert.deepEqual(pixels, getClippedPixels(myp5.P2D, mask)); }); - test('It can mask out a shape', function() { + test('It can mask out a shape', function () { const mask = () => { myp5.background(255); myp5.noStroke(); @@ -2600,7 +2629,7 @@ void main() { assert.deepEqual(pixels, getClippedPixels(myp5.P2D, mask)); }); - test('It can mask multiple shapes', function() { + test('It can mask multiple shapes', function () { const mask = () => { myp5.background(255); myp5.noStroke(); @@ -2632,7 +2661,7 @@ void main() { assert.deepEqual(pixels, getClippedPixels(myp5.P2D, mask)); }); - test('It can mask in a shape in a framebuffer', function() { + test('It can mask in a shape in a framebuffer', function () { const mask = () => { const fbo = myp5.createFramebuffer({ antialias: false }); myp5.background(255); @@ -2659,395 +2688,362 @@ void main() { assert.deepEqual(getPixel(pixels, 15, 15), [255, 0, 0, 255]); }); - test( - 'It can mask a separate shape in a framebuffer from the main canvas', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - myp5.noStroke(); - const fbo = myp5.createFramebuffer({ antialias: false }); - myp5.rectMode(myp5.CENTER); - myp5.background('red'); - expect(myp5._renderer._clipDepths.length).to.equal(0); - myp5.push(); - myp5.beginClip(); - myp5.rect(-5, -5, 20, 20); - myp5.endClip(); - expect(myp5._renderer._clipDepths.length).to.equal(1); - - fbo.begin(); - myp5.beginClip(); - myp5.rect(5, 5, 20, 20); - myp5.endClip(); - myp5.fill('blue'); - myp5.rect(0, 0, myp5.width, myp5.height); - expect(myp5._renderer._clipDepths.length).to.equal(2); - expect(myp5._renderer.drawTarget()).to.equal(fbo); - expect(fbo._isClipApplied).to.equal(true); - fbo.end(); - expect(fbo._isClipApplied).to.equal(false); - expect(myp5._renderer._clipDepths.length).to.equal(1); - expect(myp5._renderer.drawTarget()).to.equal(myp5._renderer); - expect(myp5._renderer._isClipApplied).to.equal(true); + test('It can mask a separate shape in a framebuffer from the main canvas', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + myp5.noStroke(); + const fbo = myp5.createFramebuffer({ antialias: false }); + myp5.rectMode(myp5.CENTER); + myp5.background('red'); + expect(myp5._renderer._clipDepths.length).to.equal(0); + myp5.push(); + myp5.beginClip(); + myp5.rect(-5, -5, 20, 20); + myp5.endClip(); + expect(myp5._renderer._clipDepths.length).to.equal(1); + + fbo.begin(); + myp5.beginClip(); + myp5.rect(5, 5, 20, 20); + myp5.endClip(); + myp5.fill('blue'); + myp5.rect(0, 0, myp5.width, myp5.height); + expect(myp5._renderer._clipDepths.length).to.equal(2); + expect(myp5._renderer.drawTarget()).to.equal(fbo); + expect(fbo._isClipApplied).to.equal(true); + fbo.end(); + expect(fbo._isClipApplied).to.equal(false); + expect(myp5._renderer._clipDepths.length).to.equal(1); + expect(myp5._renderer.drawTarget()).to.equal(myp5._renderer); + expect(myp5._renderer._isClipApplied).to.equal(true); + + myp5.imageMode(myp5.CENTER); + myp5.image(fbo, 0, 0); + myp5.pop(); + expect(myp5._renderer._clipDepths.length).to.equal(0); - myp5.imageMode(myp5.CENTER); - myp5.image(fbo, 0, 0); - myp5.pop(); - expect(myp5._renderer._clipDepths.length).to.equal(0); + // In the middle of the canvas, the framebuffer's clip and the + // main canvas's clip intersect, so the blue should show through + assert.deepEqual( + myp5.get(myp5.width / 2, myp5.height / 2), + [0, 0, 255, 255] + ); - // In the middle of the canvas, the framebuffer's clip and the - // main canvas's clip intersect, so the blue should show through + // To either side of the center, nothing should be on top of + // the red background color + for (const side of [-1, 1]) { assert.deepEqual( - myp5.get(myp5.width / 2, myp5.height / 2), - [0, 0, 255, 255] + myp5.get(myp5.width / 2 + side * 10, myp5.height / 2 + side * 10), + [255, 0, 0, 255] ); - - // To either side of the center, nothing should be on top of - // the red background color - for (const side of [-1, 1]) { - assert.deepEqual( - myp5.get(myp5.width / 2 + side * 10, myp5.height / 2 + side * 10), - [255, 0, 0, 255] - ); - } } - ); + }); - test( - 'Main canvas masks do not apply to framebuffers', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - const fbo = myp5.createFramebuffer({ antialias: false }); - myp5.rectMode(myp5.CENTER); - myp5.background('red'); - expect(myp5._renderer._stencilTestOn).to.equal(false); - myp5.push(); - myp5.beginClip(); - myp5.rect(-20, -20, 40, 40); - myp5.endClip(); - expect(myp5._renderer._stencilTestOn).to.equal(true); + test('Main canvas masks do not apply to framebuffers', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const fbo = myp5.createFramebuffer({ antialias: false }); + myp5.rectMode(myp5.CENTER); + myp5.background('red'); + expect(myp5._renderer._stencilTestOn).to.equal(false); + myp5.push(); + myp5.beginClip(); + myp5.rect(-20, -20, 40, 40); + myp5.endClip(); + expect(myp5._renderer._stencilTestOn).to.equal(true); - fbo.begin(); - expect(myp5._renderer._stencilTestOn).to.equal(false); - myp5.noStroke(); - myp5.fill('blue'); - myp5.rect(0, 0, myp5.width, myp5.height); - fbo.end(); + fbo.begin(); + expect(myp5._renderer._stencilTestOn).to.equal(false); + myp5.noStroke(); + myp5.fill('blue'); + myp5.rect(0, 0, myp5.width, myp5.height); + fbo.end(); - expect(myp5._renderer._stencilTestOn).to.equal(true); - myp5.pop(); - expect(myp5._renderer._stencilTestOn).to.equal(false); + expect(myp5._renderer._stencilTestOn).to.equal(true); + myp5.pop(); + expect(myp5._renderer._stencilTestOn).to.equal(false); - myp5.imageMode(myp5.CENTER); - myp5.image(fbo, 0, 0); + myp5.imageMode(myp5.CENTER); + myp5.image(fbo, 0, 0); - // In the middle of the canvas, the framebuffer's clip and the - // main canvas's clip intersect, so the blue should show through - assert.deepEqual( - myp5.get(myp5.width / 2, myp5.height / 2), - [0, 0, 255, 255] - ); - } - ); + // In the middle of the canvas, the framebuffer's clip and the + // main canvas's clip intersect, so the blue should show through + assert.deepEqual( + myp5.get(myp5.width / 2, myp5.height / 2), + [0, 0, 255, 255] + ); + }); }); - suite('vertexProperty()', function() { - test('Immediate mode data and buffers created in beginShape', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); + suite('vertexProperty()', function () { + test('Immediate mode data and buffers created in beginShape', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); - myp5.noStroke(); - myp5.beginShape(); - myp5.vertexProperty('aCustom', 1); - myp5.vertexProperty('aCustomVec3', [1, 2, 3]); - myp5.vertex(0,0,0); - myp5.vertex(0,1,0); - myp5.vertex(1,1,0); - myp5.endShape(); + myp5.noStroke(); + myp5.beginShape(); + myp5.vertexProperty('aCustom', 1); + myp5.vertexProperty('aCustomVec3', [1, 2, 3]); + myp5.vertex(0, 0, 0); + myp5.vertex(0, 1, 0); + myp5.vertex(1, 1, 0); + myp5.endShape(); - expect( - myp5._renderer.shapeBuilder.geometry.userVertexProperties.aCustom - ).to.containSubset({ - name: 'aCustom', - currentData: [1], - dataSize: 1 - }); - expect( - myp5._renderer.shapeBuilder.geometry.userVertexProperties.aCustomVec3 - ).to.containSubset({ - name: 'aCustomVec3', - currentData: [1, 2, 3], - dataSize: 3 - }); - } - ); - test('Immediate mode data and buffers deleted after beginShape', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); + expect( + myp5._renderer.shapeBuilder.geometry.userVertexProperties.aCustom + ).to.containSubset({ + name: 'aCustom', + currentData: [1], + dataSize: 1 + }); + expect( + myp5._renderer.shapeBuilder.geometry.userVertexProperties.aCustomVec3 + ).to.containSubset({ + name: 'aCustomVec3', + currentData: [1, 2, 3], + dataSize: 3 + }); + }); + test('Immediate mode data and buffers deleted after beginShape', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + myp5.beginShape(); + myp5.vertexProperty('aCustom', 1); + myp5.vertexProperty('aCustomVec3', [1, 2, 3]); + myp5.vertex(0, 0, 0); + myp5.endShape(); + + myp5.beginShape(); + myp5.endShape(); + assert.isUndefined(myp5._renderer.shapeBuilder.geometry.aCustomSrc); + assert.isUndefined(myp5._renderer.shapeBuilder.geometry.aCustomVec3Src); + assert.deepEqual( + myp5._renderer.shapeBuilder.geometry.userVertexProperties, + {} + ); + assert.deepEqual(myp5._renderer.buffers.user, []); + }); + test('Data copied over from beginGeometry', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const myGeo = myp5.buildGeometry(() => { myp5.beginShape(); myp5.vertexProperty('aCustom', 1); - myp5.vertexProperty('aCustomVec3', [1,2,3]); - myp5.vertex(0,0,0); + myp5.vertexProperty('aCustomVec3', [1, 2, 3]); + myp5.vertex(0, 1, 0); + myp5.vertex(-1, 0, 0); + myp5.vertex(1, 0, 0); myp5.endShape(); + }); + assert.deepEqual(myGeo.aCustomSrc, [1, 1, 1]); + assert.deepEqual(myGeo.aCustomVec3Src, [1, 2, 3, 1, 2, 3, 1, 2, 3]); + }); + test('Retained mode buffers are created for rendering', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); - myp5.beginShape(); - myp5.endShape(); - assert.isUndefined(myp5._renderer.shapeBuilder.geometry.aCustomSrc); - assert.isUndefined(myp5._renderer.shapeBuilder.geometry.aCustomVec3Src); - assert.deepEqual( - myp5._renderer.shapeBuilder.geometry.userVertexProperties, - {} - ); - assert.deepEqual(myp5._renderer.buffers.user, []); - } - ); - test('Data copied over from beginGeometry', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - const myGeo = myp5.buildGeometry(() => { - myp5.beginShape(); - myp5.vertexProperty('aCustom', 1); - myp5.vertexProperty('aCustomVec3', [1,2,3]); - myp5.vertex(0,1,0); - myp5.vertex(-1,0,0); - myp5.vertex(1,0,0); - myp5.endShape(); - }); - assert.deepEqual(myGeo.aCustomSrc, [1,1,1]); - assert.deepEqual(myGeo.aCustomVec3Src, [1,2,3,1,2,3,1,2,3]); - } - ); - test('Retained mode buffers are created for rendering', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - - const prevDrawFills = myp5._renderer._drawFills; - let called = false; - myp5._renderer._drawFills = function(...args) { - called = true; - expect(myp5._renderer.buffers.user).to.containSubset([ - { - size: 1, - src: 'aCustomSrc', - dst: 'aCustomBuffer', - attr: 'aCustom' - }, - { - size: 3, - src: 'aCustomVec3Src', - dst: 'aCustomVec3Buffer', - attr: 'aCustomVec3' - } - ]); + const prevDrawFills = myp5._renderer._drawFills; + let called = false; + myp5._renderer._drawFills = function (...args) { + called = true; + expect(myp5._renderer.buffers.user).to.containSubset([ + { + size: 1, + src: 'aCustomSrc', + dst: 'aCustomBuffer', + attr: 'aCustom' + }, + { + size: 3, + src: 'aCustomVec3Src', + dst: 'aCustomVec3Buffer', + attr: 'aCustomVec3' + } + ]); - prevDrawFills.apply(this, args); - }; + prevDrawFills.apply(this, args); + }; - try { - const myGeo = myp5.buildGeometry(() => { - myp5.beginShape(); - myp5.vertexProperty('aCustom', 1); - myp5.vertexProperty('aCustomVec3', [1,2,3]); - myp5.vertex(0,0,0); - myp5.vertex(1,0,0); - myp5.vertex(1,1,0); - myp5.endShape(); - }); - myp5.model(myGeo); - expect(called).to.equal(true); - } finally { - myp5._renderer._drawFills = prevDrawFills; - } - } - ); - test('Retained mode buffers deleted after rendering', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); + try { const myGeo = myp5.buildGeometry(() => { myp5.beginShape(); myp5.vertexProperty('aCustom', 1); - myp5.vertexProperty('aCustomVec3', [1,2,3]); - myp5.vertex(0,0,0); - myp5.vertex(1,0,0); - myp5.vertex(1,1,0); + myp5.vertexProperty('aCustomVec3', [1, 2, 3]); + myp5.vertex(0, 0, 0); + myp5.vertex(1, 0, 0); + myp5.vertex(1, 1, 0); myp5.endShape(); }); myp5.model(myGeo); - assert.equal(myp5._renderer.buffers.user.length, 0); + expect(called).to.equal(true); + } finally { + myp5._renderer._drawFills = prevDrawFills; } - ); - test.skip('Friendly error if different sizes used', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - const logs = []; - const myLog = (...data) => logs.push(data.join(', ')); - const oldLog = console.log; - console.log = myLog; + }); + test('Retained mode buffers deleted after rendering', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const myGeo = myp5.buildGeometry(() => { myp5.beginShape(); - myp5.vertexProperty('aCustom', [1,2,3]); - myp5.vertex(0,0,0); - myp5.vertexProperty('aCustom', [1,2]); - myp5.vertex(1,0,0); + myp5.vertexProperty('aCustom', 1); + myp5.vertexProperty('aCustomVec3', [1, 2, 3]); + myp5.vertex(0, 0, 0); + myp5.vertex(1, 0, 0); + myp5.vertex(1, 1, 0); myp5.endShape(); - console.log = oldLog; - expect(logs.join('\n')).to.match(/Custom vertex property 'aCustom' has been set with various data sizes/); - } - ); - test.skip('Friendly error too many values set', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - const logs = []; - const myLog = (...data) => logs.push(data.join(', ')); - const oldLog = console.log; - console.log = myLog; - let myGeo = new p5.Geometry(); - myGeo.gid = 'myGeo'; - myGeo.vertices.push(new myp5.createVector(0,0,0)); - myGeo.vertices.push(new myp5.createVector(1,0,0)); - myGeo.vertices.push(new myp5.createVector(1,1,0)); - myGeo.vertexProperty('aCustom', 1); - myGeo.vertexProperty('aCustom', 2); - myGeo.vertexProperty('aCustom', 3); - myGeo.vertexProperty('aCustom', 4); - myp5.model(myGeo); - console.log = oldLog; - expect(logs.join('\n')).to.match(/One of the geometries has a custom vertex property 'aCustom' with more values than vertices./); - } - ); - test.skip('Friendly error if too few values set', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - const logs = []; - const myLog = (...data) => logs.push(data.join(', ')); - const oldLog = console.log; - console.log = myLog; - let myGeo = new p5.Geometry(); - myGeo.gid = 'myGeo'; - myGeo.vertices.push(new myp5.createVector(0,0,0)); - myGeo.vertices.push(new myp5.createVector(1,0,0)); - myGeo.vertices.push(new myp5.createVector(1,1,0)); - myGeo.vertexProperty('aCustom', 1); - myp5.model(myGeo); - console.log = oldLog; - expect(logs.join('\n')).to.match(/One of the geometries has a custom vertex property 'aCustom' with fewer values than vertices./); - } - ); + }); + myp5.model(myGeo); + assert.equal(myp5._renderer.buffers.user.length, 0); + }); + test.skip('Friendly error if different sizes used', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const logs = []; + const myLog = (...data) => logs.push(data.join(', ')); + const oldLog = console.log; + console.log = myLog; + myp5.beginShape(); + myp5.vertexProperty('aCustom', [1, 2, 3]); + myp5.vertex(0, 0, 0); + myp5.vertexProperty('aCustom', [1, 2]); + myp5.vertex(1, 0, 0); + myp5.endShape(); + console.log = oldLog; + expect(logs.join('\n')).to.match( + /Custom vertex property 'aCustom' has been set with various data sizes/ + ); + }); + test.skip('Friendly error too many values set', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const logs = []; + const myLog = (...data) => logs.push(data.join(', ')); + const oldLog = console.log; + console.log = myLog; + let myGeo = new p5.Geometry(); + myGeo.gid = 'myGeo'; + myGeo.vertices.push(new myp5.createVector(0, 0, 0)); + myGeo.vertices.push(new myp5.createVector(1, 0, 0)); + myGeo.vertices.push(new myp5.createVector(1, 1, 0)); + myGeo.vertexProperty('aCustom', 1); + myGeo.vertexProperty('aCustom', 2); + myGeo.vertexProperty('aCustom', 3); + myGeo.vertexProperty('aCustom', 4); + myp5.model(myGeo); + console.log = oldLog; + expect(logs.join('\n')).to.match( + /One of the geometries has a custom vertex property 'aCustom' with more values than vertices./ + ); + }); + test.skip('Friendly error if too few values set', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const logs = []; + const myLog = (...data) => logs.push(data.join(', ')); + const oldLog = console.log; + console.log = myLog; + let myGeo = new p5.Geometry(); + myGeo.gid = 'myGeo'; + myGeo.vertices.push(new myp5.createVector(0, 0, 0)); + myGeo.vertices.push(new myp5.createVector(1, 0, 0)); + myGeo.vertices.push(new myp5.createVector(1, 1, 0)); + myGeo.vertexProperty('aCustom', 1); + myp5.model(myGeo); + console.log = oldLog; + expect(logs.join('\n')).to.match( + /One of the geometries has a custom vertex property 'aCustom' with fewer values than vertices./ + ); + }); }); - suite('Stencil Test Tracking', function() { - test('Stencil test is disabled by default', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - const gl = myp5._renderer.GL; - const isEnabled = gl.isEnabled(gl.STENCIL_TEST); + suite('Stencil Test Tracking', function () { + test('Stencil test is disabled by default', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const gl = myp5._renderer.GL; + const isEnabled = gl.isEnabled(gl.STENCIL_TEST); - assert.equal(isEnabled, false); - assert.equal(myp5._renderer._userEnabledStencil, false); - } - ); + assert.equal(isEnabled, false); + assert.equal(myp5._renderer._userEnabledStencil, false); + }); - test('Tracks when user manually enables stencil test', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - const gl = myp5._renderer.GL; + test('Tracks when user manually enables stencil test', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const gl = myp5._renderer.GL; - gl.enable(gl.STENCIL_TEST); - assert.equal(myp5._renderer._userEnabledStencil, true); - assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); - } - ); + gl.enable(gl.STENCIL_TEST); + assert.equal(myp5._renderer._userEnabledStencil, true); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); + }); - test('Tracks when user manually disables stencil test', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - const gl = myp5._renderer.GL; + test('Tracks when user manually disables stencil test', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const gl = myp5._renderer.GL; - gl.enable(gl.STENCIL_TEST); - gl.disable(gl.STENCIL_TEST); + gl.enable(gl.STENCIL_TEST); + gl.disable(gl.STENCIL_TEST); - assert.equal(myp5._renderer._userEnabledStencil, false); - assert.equal(gl.isEnabled(gl.STENCIL_TEST), false); - } - ); + assert.equal(myp5._renderer._userEnabledStencil, false); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), false); + }); - test('Maintains stencil test state across draw cycles when user enabled', - function() { - let drawCalled = false; + test('Maintains stencil test state across draw cycles when user enabled', function () { + let drawCalled = false; - myp5.createCanvas(50, 50, myp5.WEBGL); - const originalDraw = myp5.draw; + myp5.createCanvas(50, 50, myp5.WEBGL); + const originalDraw = myp5.draw; - myp5.draw = function() { - drawCalled = true; - if (originalDraw) originalDraw.call(myp5); - }; + myp5.draw = function () { + drawCalled = true; + if (originalDraw) originalDraw.call(myp5); + }; - const gl = myp5._renderer.GL; - gl.enable(gl.STENCIL_TEST); - assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); - myp5.redraw(); + const gl = myp5._renderer.GL; + gl.enable(gl.STENCIL_TEST); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); + myp5.redraw(); - assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); - assert.equal(myp5._renderer._userEnabledStencil, true); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); + assert.equal(myp5._renderer._userEnabledStencil, true); - myp5.draw = originalDraw; - } - ); + myp5.draw = originalDraw; + }); - test('Internal clip operations preserve user stencil test setting', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - const gl = myp5._renderer.GL; + test('Internal clip operations preserve user stencil test setting', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const gl = myp5._renderer.GL; - gl.enable(gl.STENCIL_TEST); + gl.enable(gl.STENCIL_TEST); - myp5.push(); - myp5.clip(() => { - myp5.rect(0, 0, 10, 10); - }); - assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); - myp5.pop(); + myp5.push(); + myp5.clip(() => { + myp5.rect(0, 0, 10, 10); + }); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); + myp5.pop(); - assert.equal(myp5._renderer._userEnabledStencil, true); - assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); - } - ); + assert.equal(myp5._renderer._userEnabledStencil, true); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); + }); - test('Internal clip operations do not enable stencil test for future draw cycles', - function() { - myp5.createCanvas(50, 50, myp5.WEBGL); - const gl = myp5._renderer.GL; + test('Internal clip operations do not enable stencil test for future draw cycles', function () { + myp5.createCanvas(50, 50, myp5.WEBGL); + const gl = myp5._renderer.GL; - gl.disable(gl.STENCIL_TEST); - assert.equal(myp5._renderer._userEnabledStencil, false); + gl.disable(gl.STENCIL_TEST); + assert.equal(myp5._renderer._userEnabledStencil, false); - myp5.push(); - myp5.clip(() => { - myp5.rect(0, 0, 10, 10); - }); - assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); - myp5.pop(); + myp5.push(); + myp5.clip(() => { + myp5.rect(0, 0, 10, 10); + }); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), true); + myp5.pop(); - myp5.redraw(); + myp5.redraw(); - assert.equal(myp5._renderer._userEnabledStencil, false); - assert.equal(gl.isEnabled(gl.STENCIL_TEST), false); - } - ); + assert.equal(myp5._renderer._userEnabledStencil, false); + assert.equal(gl.isEnabled(gl.STENCIL_TEST), false); + }); }); - suite('Matrix getters', function() { - test('uModelMatrix', function() { + suite('Matrix getters', function () { + test('uModelMatrix', function () { p5.registerAddon(function (p5, fn) { - fn.checkModelMatrix = function() { + fn.checkModelMatrix = function () { assert.deepEqual( [...this._renderer.uModelMatrix.mat4], - [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 5, 0, 0, 1 - ] + [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 0, 0, 1] ); }; }); @@ -3057,17 +3053,12 @@ void main() { myp5.checkModelMatrix(); }); - test('uViewMatrix', function() { + test('uViewMatrix', function () { p5.registerAddon(function (p5, fn) { - fn.checkViewMatrix = function() { + fn.checkViewMatrix = function () { assert.deepEqual( [...this._renderer.uViewMatrix.mat4], - [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, -500, 1 - ] + [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -500, 1] ); }; }); @@ -3077,17 +3068,12 @@ void main() { myp5.checkViewMatrix(); }); - test('uMVMatrix', function() { + test('uMVMatrix', function () { p5.registerAddon(function (p5, fn) { - fn.checkMVMatrix = function() { + fn.checkMVMatrix = function () { assert.deepEqual( [...this._renderer.uMVMatrix.mat4], - [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 5, 0, -500, 1 - ] + [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 0, -500, 1] ); }; }); @@ -3097,16 +3083,14 @@ void main() { myp5.checkMVMatrix(); }); - test('uPMatrix', function() { + test('uPMatrix', function () { p5.registerAddon(function (p5, fn) { - fn.checkPMatrix = function() { + fn.checkPMatrix = function () { assert.deepEqual( [...this._renderer.uPMatrix.mat4], [ - 32, 0, 0, 0, - 0, -32, 0, 0, - 0, 0, -1.0202020406723022, -1, - 0, 0, -161.6161651611328, 0 + 32, 0, 0, 0, 0, -32, 0, 0, 0, 0, -1.0202020406723022, -1, 0, 0, + -161.6161651611328, 0 ] ); }; @@ -3116,8 +3100,8 @@ void main() { }); }); - suite('buildGeometry', function() { - test('captures geometry without drawing', function() { + suite('buildGeometry', function () { + test('captures geometry without drawing', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.background(255, 0, 0); const geom = myp5.buildGeometry(() => myp5.circle(0, 0, 5)); @@ -3125,7 +3109,7 @@ void main() { expect(myp5.get(5, 5)).toEqual([255, 0, 0, 255]); }); - test('returns fill state back to a previous color', function() { + test('returns fill state back to a previous color', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.background(255, 0, 0); myp5.fill(0, 255, 0); @@ -3133,7 +3117,7 @@ void main() { myp5.model(geom); expect(myp5.get(5, 5)).toEqual([0, 255, 0, 255]); }); - test('returns fill state back to no fill', function() { + test('returns fill state back to no fill', function () { myp5.createCanvas(10, 10, myp5.WEBGL); myp5.background(255, 0, 0); myp5.noFill(); @@ -3141,7 +3125,7 @@ void main() { myp5.model(geom); expect(myp5.get(5, 5)).toEqual([255, 0, 0, 255]); }); - test('does not throw with a large number of vertices', function() { + test('does not throw with a large number of vertices', function () { myp5.createCanvas(10, 10, myp5.WEBGL); // Enough triangles to exceed the ~65k argument limit of Function.prototype.apply, // which would cause a stack overflow if vertices were spread into push() calls. @@ -3160,8 +3144,8 @@ void main() { }); }); - suite('fontWidth', function() { - test('respects textSize changes across push/pop', async function() { + suite('fontWidth', function () { + test('respects textSize changes across push/pop', async function () { myp5.createCanvas(100, 100, myp5.WEBGL); const font = await myp5.loadFont('test/unit/assets/acmesa.ttf'); @@ -3178,7 +3162,7 @@ void main() { expect(widthAt20).toBeGreaterThan(widthAt12); }); - test('fontWidth restores correctly when font is unset inside push/pop', async function() { + test('fontWidth restores correctly when font is unset inside push/pop', async function () { myp5.createCanvas(100, 100, myp5.WEBGL); const font = await myp5.loadFont('test/unit/assets/acmesa.ttf'); myp5.textFont(font); diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index fb06292377..1a2f54de0b 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -10,29 +10,31 @@ vi.mock('../../../src/strands/strands_FES', () => { }; return { userError, - internalError: (msg) => { throw new Error(`[p5.strands internal error]: ${msg}`); }, + internalError: msg => { + throw new Error(`[p5.strands internal error]: ${msg}`); + }, dimensionMismatchError: (declaredDim, actualDim, varName) => { userError( 'dimension mismatch', `Cannot assign a value of dimension ${actualDim} to \`${varName}\`, which expects dimension ${declaredDim}.` ); - }, + } }; }); -suite('p5.Shader', function() { +suite('p5.Shader', function () { var myp5; - beforeAll(function() { + beforeAll(function () { window.IS_MINIFIED = true; - myp5 = new p5(function(p) { - p.setup = function() { + myp5 = new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100, p.WEBGL); p.pointLight(250, 250, 250, 100, 100, 0); p.ambientMaterial(250); }; }); }); - var testUniforms = function(shaderName, uniforms, expectedUniforms) { + var testUniforms = function (shaderName, uniforms, expectedUniforms) { // assert(expectedUniforms.length === Object.keys(uniforms).length, // shaderName + ' expected ' + expectedUniforms.length + ' uniforms but has ' + // Object.keys(uniforms).length); @@ -45,7 +47,7 @@ suite('p5.Shader', function() { ); } }; - var testAttributes = function(shaderName, attributes, expectedAttributes) { + var testAttributes = function (shaderName, attributes, expectedAttributes) { // assert(expectedAttributes.length === Object.keys(attributes).length, // shaderName + ' expected ' + expectedAttributes.length + // ' attributes but has ' + Object.keys(attributes).length); @@ -58,7 +60,7 @@ suite('p5.Shader', function() { ); } }; - var testShader = function( + var testShader = function ( shaderName, shaderObj, expectedAttributes, @@ -69,11 +71,11 @@ suite('p5.Shader', function() { testUniforms(shaderName, shaderObj.uniforms, expectedUniforms); shaderObj.unbindShader(); }; - afterAll(function() { + afterAll(function () { myp5.remove(); }); - suite('Shader', function() { - test('Light Shader', function() { + suite('Shader', function () { + test('Light Shader', function () { var expectedAttributes = ['aPosition', 'aNormal', 'aTexCoord']; var expectedUniforms = [ 'uModelViewMatrix', @@ -111,7 +113,7 @@ suite('p5.Shader', function() { expectedUniforms ); }); - test('Color Shader definition', function() { + test('Color Shader definition', function () { var expectedAttributes = ['aPosition']; var expectedUniforms = [ 'uModelViewMatrix', @@ -125,12 +127,9 @@ suite('p5.Shader', function() { expectedUniforms ); }); - test('Immediate Mode Shader definition', function() { + test('Immediate Mode Shader definition', function () { var expectedAttributes = ['aPosition', 'aVertexColor']; - var expectedUniforms = [ - 'uModelViewMatrix', - 'uProjectionMatrix' - ]; + var expectedUniforms = ['uModelViewMatrix', 'uProjectionMatrix']; testShader( 'Immediate Mode Shader', myp5._renderer._getColorShader(), @@ -138,7 +137,7 @@ suite('p5.Shader', function() { expectedUniforms ); }); - test('Normal Shader definition', function() { + test('Normal Shader definition', function () { var expectedAttributes = ['aPosition', 'aNormal']; var expectedUniforms = [ 'uModelViewMatrix', @@ -152,7 +151,7 @@ suite('p5.Shader', function() { expectedUniforms ); }); - test('Color Shader is set after fill()', function() { + test('Color Shader is set after fill()', function () { myp5.fill(0); var retainedColorShader = myp5._renderer._getColorShader(); var texLightShader = myp5._renderer._getLightShader(); @@ -172,7 +171,7 @@ suite('p5.Shader', function() { "_renderer's immediate mode shader was not color shader after fill" ); }); - test('Normal Shader is set after normalMaterial()', function() { + test('Normal Shader is set after normalMaterial()', function () { myp5.normalMaterial(); var normalShader = myp5._renderer._getNormalShader(); var selectedRetainedShader = myp5._renderer._getFillShader(); @@ -186,7 +185,7 @@ suite('p5.Shader', function() { "_renderer's retain mode shader was not normal shader" ); }); - test('Light shader set after ambientMaterial()', function() { + test('Light shader set after ambientMaterial()', function () { var lightShader = myp5._renderer._getLightShader(); myp5.ambientMaterial(128); var selectedRetainedShader = myp5._renderer._getFillShader(); @@ -202,7 +201,7 @@ suite('p5.Shader', function() { 'after call to ambientMaterial()' ); }); - test('Light shader set after specularMaterial()', function() { + test('Light shader set after specularMaterial()', function () { var lightShader = myp5._renderer._getLightShader(); myp5.specularMaterial(128); var selectedRetainedShader = myp5._renderer._getFillShader(); @@ -218,7 +217,7 @@ suite('p5.Shader', function() { 'after call to specularMaterial()' ); }); - test('Light shader set after emissiveMaterial()', function() { + test('Light shader set after emissiveMaterial()', function () { var lightShader = myp5._renderer._getLightShader(); myp5.emissiveMaterial(128); var selectedRetainedShader = myp5._renderer._getFillShader(); @@ -234,18 +233,18 @@ suite('p5.Shader', function() { 'after call to emissiveMaterial()' ); }); - test('Able to setUniform empty arrays', function() { + test('Able to setUniform empty arrays', function () { myp5.shader(myp5._renderer._getLightShader()); var s = myp5._renderer.states.userFillShader; s.setUniform('uMaterialColor', []); s.setUniform('uLightingDirection', []); }); - test('Able to set shininess', function() { + test('Able to set shininess', function () { assert.deepEqual(myp5._renderer.states._useShininess, 1); myp5.shininess(50); assert.deepEqual(myp5._renderer.states._useShininess, 50); }); - test('Shader is reset after resetShader is called', function() { + test('Shader is reset after resetShader is called', function () { myp5.shader(myp5._renderer._getColorShader()); var prevShader = myp5._renderer.states.userFillShader; assert.isTrue(prevShader !== null); @@ -253,7 +252,7 @@ suite('p5.Shader', function() { var curShader = myp5._renderer.states.userFillShader; assert.isTrue(curShader === null); }); - test('version() detects a #version directive', function() { + test('version() detects a #version directive', function () { const shader = myp5.createShader( ` #version 300 es @@ -276,9 +275,9 @@ suite('p5.Shader', function() { ); assert.strictEqual(shader.version(), '300 es'); }); - suite('Hooks', function() { + suite('Hooks', function () { let myShader; - beforeEach(function() { + beforeEach(function () { myShader = myp5.createShader( ` precision highp float; @@ -319,7 +318,7 @@ suite('p5.Shader', function() { } ); }); - test('available hooks show up in inspectHooks()', function() { + test('available hooks show up in inspectHooks()', function () { const logs = []; const myLog = (...data) => logs.push(data.join(', ')); const oldLog = console.log; @@ -328,24 +327,28 @@ suite('p5.Shader', function() { console.log = oldLog; expect(logs.join('\n')).to.match(/vec4 getVertexColor/); }); - test('unfilled hooks do not have an AUGMENTED_HOOK define', function() { + test('unfilled hooks do not have an AUGMENTED_HOOK define', function () { const modified = myShader.modify({}); - expect(modified.fragSrc()).not.to.match(/#define AUGMENTED_HOOK_getVertexColor/); + expect(modified.fragSrc()).not.to.match( + /#define AUGMENTED_HOOK_getVertexColor/ + ); }); - test('anonymous function shaderModifier does not throw when parsed', function() { - const callModify = () => myShader.modify(function() {}); + test('anonymous function shaderModifier does not throw when parsed', function () { + const callModify = () => myShader.modify(function () {}); expect(callModify).not.toThrowError(); }); - test('filled hooks do have an AUGMENTED_HOOK define', function() { + test('filled hooks do have an AUGMENTED_HOOK define', function () { const modified = myShader.modify({ 'vec4 getVertexColor': `(vec4 c) { return vec4(1., 0., 0., 1.); }` }); - expect(modified.fragSrc()).to.match(/#define AUGMENTED_HOOK_getVertexColor/); + expect(modified.fragSrc()).to.match( + /#define AUGMENTED_HOOK_getVertexColor/ + ); }); }); - test('framebuffer textures are unbound when you draw to the framebuffer', function() { + test('framebuffer textures are unbound when you draw to the framebuffer', function () { const sh = myp5.baseMaterialShader().modify({ uniforms: { 'sampler2D myTex': null @@ -364,8 +367,8 @@ suite('p5.Shader', function() { assert.deepEqual(myp5.get(0, 0), [255, 0, 0, 255]); }); }); - suite('hookTypes', function() { - test('Produces expected types on baseFilterShader()', function() { + suite('hookTypes', function () { + test('Produces expected types on baseFilterShader()', function () { const types = myp5.baseFilterShader().hookTypes('vec4 getColor'); assert.deepEqual(types, { name: 'getColor', @@ -456,12 +459,7 @@ suite('p5.Shader', function() { myp5.createCanvas(5, 5, myp5.WEBGL); function myMaterial() { myp5.getPixelInputs(inputs => { - inputs.color = [ - 1, - 0, - 0, - 1 - ]; + inputs.color = [1, 0, 0, 1]; return inputs; }); } @@ -474,17 +472,16 @@ suite('p5.Shader', function() { test('does not break when arrays are in uniform callbacks', () => { myp5.createCanvas(5, 5, myp5.WEBGL); - const myShader = myp5.baseMaterialShader().modify(() => { - const size = myp5.uniformVector2(() => [myp5.width, myp5.height]); - myp5.getPixelInputs(inputs => { - inputs.color = [ - size / 1000, - 0, - 1 - ]; - return inputs; - }); - }, { myp5 }); + const myShader = myp5.baseMaterialShader().modify( + () => { + const size = myp5.uniformVector2(() => [myp5.width, myp5.height]); + myp5.getPixelInputs(inputs => { + inputs.color = [size / 1000, 0, 1]; + return inputs; + }); + }, + { myp5 } + ); expect(() => { myp5.shader(myShader); myp5.plane(myp5.width, myp5.height); @@ -494,13 +491,16 @@ suite('p5.Shader', function() { test('buildFilterShader can use numeric constants from scope', () => { myp5.createCanvas(5, 5, myp5.WEBGL); const constants = { val: 100 }; - const myShader = myp5.buildFilterShader(({ constants }) => { - filterColor.begin(); - let c = 0; - c += constants.val / 255; - filterColor.set([c, c, c, 1]); - filterColor.end(); - }, { constants }); + const myShader = myp5.buildFilterShader( + ({ constants }) => { + filterColor.begin(); + let c = 0; + c += constants.val / 255; + filterColor.set([c, c, c, 1]); + filterColor.end(); + }, + { constants } + ); expect(() => { myp5.filter(myShader); }).not.toThrowError(); @@ -509,63 +509,75 @@ suite('p5.Shader', function() { test('buildMaterialShader forwards scope to modify', () => { myp5.createCanvas(5, 5, myp5.WEBGL); expect(() => { - const myShader = myp5.buildMaterialShader(() => { - myp5.getPixelInputs(inputs => { - inputs.color = [1, 0, 0, 1]; - return inputs; - }); - }, { myp5 }); + const myShader = myp5.buildMaterialShader( + () => { + myp5.getPixelInputs(inputs => { + inputs.color = [1, 0, 0, 1]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(myShader); myp5.plane(myp5.width, myp5.height); }).not.toThrowError(); }); -test('returns numbers for builtin globals outside hooks and a strandNode when called inside hooks', () => { - myp5.createCanvas(5, 5, myp5.WEBGL); - myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const mxInHook = myp5.mouseX; - const wInHook = myp5.width; - assert.isTrue(mxInHook.isStrandsNode); - assert.isTrue(wInHook.isStrandsNode); - inputs.color = [1, 0, 0, 1]; - return inputs; - }); - }, { myp5 }); - - const mx = myp5.mouseX; - const w = myp5.width; - assert.isNumber(mx); - assert.isNumber(w); - assert.strictEqual(w, myp5.width); -}); + test('returns numbers for builtin globals outside hooks and a strandNode when called inside hooks', () => { + myp5.createCanvas(5, 5, myp5.WEBGL); + myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const mxInHook = myp5.mouseX; + const wInHook = myp5.width; + assert.isTrue(mxInHook.isStrandsNode); + assert.isTrue(wInHook.isStrandsNode); + inputs.color = [1, 0, 0, 1]; + return inputs; + }); + }, + { myp5 } + ); + + const mx = myp5.mouseX; + const w = myp5.width; + assert.isNumber(mx); + assert.isNumber(w); + assert.strictEqual(w, myp5.width); + }); test('instanceIndex is a value and instanceID() is a compatibility alias', () => { myp5.createCanvas(5, 5, myp5.WEBGL); - myp5.baseMaterialShader().modify(() => { - myp5.getWorldInputs(inputs => { - // instanceIndex is a property — no parentheses - const idx = myp5.instanceIndex; - assert.isTrue(idx.isStrandsNode); + myp5.baseMaterialShader().modify( + () => { + myp5.getWorldInputs(inputs => { + // instanceIndex is a property — no parentheses + const idx = myp5.instanceIndex; + assert.isTrue(idx.isStrandsNode); - // instanceID() is a function kept for compatibility - const idxCompat = myp5.instanceID(); - assert.isTrue(idxCompat.isStrandsNode); + // instanceID() is a function kept for compatibility + const idxCompat = myp5.instanceID(); + assert.isTrue(idxCompat.isStrandsNode); - return inputs; - }); - }, { myp5 }); + return inputs; + }); + }, + { myp5 } + ); }); test('map() works inside a strands modify callback', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const v = myp5.map(0.25, 0, 1, 0, 1); - inputs.color = [v, v, v, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const v = myp5.map(0.25, 0, 1, 0, 1); + inputs.color = [v, v, v, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -579,13 +591,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('map() with withinBounds clamps the result inside strands', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const v = myp5.map(2.0, 0, 1, 0, 1, true); - inputs.color = [v, v, v, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const v = myp5.map(2.0, 0, 1, 0, 1, true); + inputs.color = [v, v, v, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -599,13 +614,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('map() shrinks a wider input range into a narrower output range', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const v = myp5.map(5, 0, 10, 0, 1); - inputs.color = [v, v, v, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const v = myp5.map(5, 0, 10, 0, 1); + inputs.color = [v, v, v, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -619,13 +637,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('map() handles offset output ranges correctly', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const v = myp5.map(0.5, 0, 1, 0.2, 0.8); - inputs.color = [v, v, v, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const v = myp5.map(0.5, 0, 1, 0.2, 0.8); + inputs.color = [v, v, v, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -639,13 +660,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('map() handles a negative input range', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const v = myp5.map(0, -1, 1, 0, 1); - inputs.color = [v, v, v, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const v = myp5.map(0, -1, 1, 0, 1); + inputs.color = [v, v, v, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -659,13 +683,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('map() remaps texCoord.x into a horizontal gradient', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const v = myp5.map(inputs.texCoord.x, 0, 1, 0.2, 0.8); - inputs.color = [v, v, v, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const v = myp5.map(inputs.texCoord.x, 0, 1, 0.2, 0.8); + inputs.color = [v, v, v, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -680,13 +707,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('color() with hex string returns correct vec4 in strands', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const c = myp5.color('#ff0000'); - inputs.color = [c.x, c.y, c.z, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const c = myp5.color('#ff0000'); + inputs.color = [c.x, c.y, c.z, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -700,13 +730,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('color() with CSS named color returns correct vec4 in strands', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const c = myp5.color('blue'); - inputs.color = [c.x, c.y, c.z, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const c = myp5.color('blue'); + inputs.color = [c.x, c.y, c.z, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -720,15 +753,18 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('lerpColor() interpolates between two colors in strands', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const c1 = myp5.color('#ff0000'); - const c2 = myp5.color('#0000ff'); - const mixed = myp5.lerpColor(c1, c2, 0.5); - inputs.color = [mixed.x, mixed.y, mixed.z, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const c1 = myp5.color('#ff0000'); + const c2 = myp5.color('#0000ff'); + const mixed = myp5.lerpColor(c1, c2, 0.5); + inputs.color = [mixed.x, mixed.y, mixed.z, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -742,16 +778,19 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('red(), green(), blue(), alpha() extract correct channels in strands', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const c = myp5.color('#ff8000'); - const r = myp5.red(c); - const g = myp5.green(c); - const b = myp5.blue(c); - inputs.color = [r, g, b, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const c = myp5.color('#ff8000'); + const r = myp5.red(c); + const g = myp5.green(c); + const b = myp5.blue(c); + inputs.color = [r, g, b, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -765,15 +804,18 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('hue() returns normalized hue value in strands', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - // Pure red has hue 0 - const c = myp5.color('#ff0000'); - const h = myp5.hue(c); - inputs.color = [h, h, h, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + // Pure red has hue 0 + const c = myp5.color('#ff0000'); + const h = myp5.hue(c); + inputs.color = [h, h, h, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -785,14 +827,17 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle custom uniform names with automatic values', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - // Variable name is 'brightness' but uniform name is 'customBrightness' - const brightness = myp5.uniformFloat('customBrightness', () => 0.8); - myp5.getPixelInputs(inputs => { - inputs.color = [brightness, brightness, brightness, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + // Variable name is 'brightness' but uniform name is 'customBrightness' + const brightness = myp5.uniformFloat('customBrightness', () => 0.8); + myp5.getPixelInputs(inputs => { + inputs.color = [brightness, brightness, brightness, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -807,14 +852,17 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle custom uniform names with manual setUniform', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - // Variable name is 'brightness' but uniform name is 'customBrightness' - const brightness = myp5.uniformFloat('customBrightness'); - myp5.getPixelInputs(inputs => { - inputs.color = [brightness, brightness, brightness, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + // Variable name is 'brightness' but uniform name is 'customBrightness' + const brightness = myp5.uniformFloat('customBrightness'); + myp5.getPixelInputs(inputs => { + inputs.color = [brightness, brightness, brightness, 1.0]; + return inputs; + }); + }, + { myp5 } + ); // Set the uniform using the custom name testShader.setUniform('customBrightness', 0.6); @@ -837,17 +885,20 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('indexing into array returned from helper function works in WebGL', () => { myp5.createCanvas(5, 5, myp5.WEBGL); - const myShader = myp5.baseMaterialShader().modify(() => { - const brightness = myp5.uniformFloat(); - function getArr() { - return [1, 2]; - } - const arr = getArr(); - myp5.getPixelInputs(inputs => { - inputs.color = [arr[0] * brightness, arr[1], 0, 1]; - return inputs; - }); - }, { myp5 }); + const myShader = myp5.baseMaterialShader().modify( + () => { + const brightness = myp5.uniformFloat(); + function getArr() { + return [1, 2]; + } + const arr = getArr(); + myp5.getPixelInputs(inputs => { + inputs.color = [arr[0] * brightness, arr[1], 0, 1]; + return inputs; + }); + }, + { myp5 } + ); expect(() => { myp5.shader(myShader); myp5.plane(myp5.width, myp5.height); @@ -856,13 +907,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('inline literal indexing [1, 2][0] works in WebGL', () => { myp5.createCanvas(5, 5, myp5.WEBGL); - const myShader = myp5.baseMaterialShader().modify(() => { - const brightness = myp5.uniformFloat(); - myp5.getPixelInputs(inputs => { - inputs.color = [[1, 2][0] * brightness, 0, 0, 1]; - return inputs; - }); - }, { myp5 }); + const myShader = myp5.baseMaterialShader().modify( + () => { + const brightness = myp5.uniformFloat(); + myp5.getPixelInputs(inputs => { + inputs.color = [[1, 2][0] * brightness, 0, 0, 1]; + return inputs; + }); + }, + { myp5 } + ); expect(() => { myp5.shader(myShader); myp5.plane(myp5.width, myp5.height); @@ -872,59 +926,74 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('array literal with 1 element throws descriptive error in WebGL', () => { myp5.createCanvas(5, 5, myp5.WEBGL); expect(() => { - myp5.baseMaterialShader().modify(() => { - const arr = [1]; - myp5.getPixelInputs(inputs => { - inputs.color = [arr[0], 0, 0, 1]; - return inputs; - }); - }, { myp5 }); + myp5.baseMaterialShader().modify( + () => { + const arr = [1]; + myp5.getPixelInputs(inputs => { + inputs.color = [arr[0], 0, 0, 1]; + return inputs; + }); + }, + { myp5 } + ); }).toThrowError('and must have 2-4 elements (got 1)'); }); test('array literal with 5 elements throws descriptive error in WebGL', () => { myp5.createCanvas(5, 5, myp5.WEBGL); expect(() => { - myp5.baseMaterialShader().modify(() => { - const arr = [1, 2, 3, 4, 5]; - myp5.getPixelInputs(inputs => { - inputs.color = [arr[0], 0, 0, 1]; - return inputs; - }); - }, { myp5 }); + myp5.baseMaterialShader().modify( + () => { + const arr = [1, 2, 3, 4, 5]; + myp5.getPixelInputs(inputs => { + inputs.color = [arr[0], 0, 0, 1]; + return inputs; + }); + }, + { myp5 } + ); }).toThrowError('and must have 2-4 elements (got 5)'); }); test('valid array lengths 2, 3, 4 work in WebGL', () => { myp5.createCanvas(5, 5, myp5.WEBGL); expect(() => { - const s2 = myp5.baseMaterialShader().modify(() => { - const arr = [1, 2]; - myp5.getPixelInputs(inputs => { - inputs.color = [arr[0], 0, 0, 1]; - return inputs; - }); - }, { myp5 }); + const s2 = myp5.baseMaterialShader().modify( + () => { + const arr = [1, 2]; + myp5.getPixelInputs(inputs => { + inputs.color = [arr[0], 0, 0, 1]; + return inputs; + }); + }, + { myp5 } + ); myp5.shader(s2); myp5.plane(myp5.width, myp5.height); - const s3 = myp5.baseMaterialShader().modify(() => { - const arr = [1, 2, 3]; - myp5.getPixelInputs(inputs => { - inputs.color = [arr[0], 0, 0, 1]; - return inputs; - }); - }, { myp5 }); + const s3 = myp5.baseMaterialShader().modify( + () => { + const arr = [1, 2, 3]; + myp5.getPixelInputs(inputs => { + inputs.color = [arr[0], 0, 0, 1]; + return inputs; + }); + }, + { myp5 } + ); myp5.shader(s3); myp5.plane(myp5.width, myp5.height); - const s4 = myp5.baseMaterialShader().modify(() => { - const arr = [1, 2, 3, 4]; - myp5.getPixelInputs(inputs => { - inputs.color = [arr[0], 0, 0, 1]; - return inputs; - }); - }, { myp5 }); + const s4 = myp5.baseMaterialShader().modify( + () => { + const arr = [1, 2, 3, 4]; + myp5.getPixelInputs(inputs => { + inputs.color = [arr[0], 0, 0, 1]; + return inputs; + }); + }, + { myp5 } + ); myp5.shader(s4); myp5.plane(myp5.width, myp5.height); }).not.toThrowError(); @@ -934,17 +1003,20 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca suite('if statement conditionals', () => { test('handle simple if statement with true condition', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.5); // initial gray - if (condition > 0.5) { - color = myp5.float(1.0); // set to white in if branch - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.5); // initial gray + if (condition > 0.5) { + color = myp5.float(1.0); // set to white in if branch + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -956,17 +1028,20 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle simple if statement with condition that is not a swizzle', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const x = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.5); // initial gray - if (x > 0.5) { - color = myp5.float(1.0); // set to white in if branch - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const x = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.5); // initial gray + if (x > 0.5) { + color = myp5.float(1.0); // set to white in if branch + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -978,17 +1053,20 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle simple if statement with simpler assignment', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let color = 1; // initial gray - if (condition > 0.5) { - color = 1; // set to white in if branch - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let color = 1; // initial gray + if (condition > 0.5) { + color = 1; // set to white in if branch + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1000,17 +1078,20 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle simple if statement with false condition', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 0.0); // false condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.5); // initial gray - if (condition > 0.5) { - color = myp5.float(1.0); // set to white in if branch - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 0.0); // false condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.5); // initial gray + if (condition > 0.5) { + color = myp5.float(1.0); // set to white in if branch + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1022,19 +1103,22 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle if-else statement', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 0.0); // false condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.5); // initial gray - if (condition > 0.5) { - color = myp5.float(1.0); // white for true - } else { - color = myp5.float(0.0); // black for false - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 0.0); // false condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.5); // initial gray + if (condition > 0.5) { + color = myp5.float(1.0); // white for true + } else { + color = myp5.float(0.0); // black for false + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1046,21 +1130,24 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle multiple variable assignments in if statement', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let red = myp5.float(0.0); - let green = myp5.float(0.0); - let blue = myp5.float(0.0); - if (condition > 0.5) { - red = myp5.float(1.0); - green = myp5.float(0.5); - blue = myp5.float(0.0); - } - inputs.color = [red, green, blue, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let red = myp5.float(0.0); + let green = myp5.float(0.0); + let blue = myp5.float(0.0); + if (condition > 0.5) { + red = myp5.float(1.0); + green = myp5.float(0.5); + blue = myp5.float(0.0); + } + inputs.color = [red, green, blue, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1068,29 +1155,32 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca const pixelColor = myp5.get(25, 25); assert.approximately(pixelColor[0], 255, 5); // Red channel should be 255 assert.approximately(pixelColor[1], 127, 5); // Green channel should be ~127 - assert.approximately(pixelColor[2], 0, 5); // Blue channel should be ~0 + assert.approximately(pixelColor[2], 0, 5); // Blue channel should be ~0 }); test('handle modifications after if statement', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); // start with black - if (condition > 0.5) { - color = myp5.float(1.0); // set to white in if branch - } else { - color = myp5.float(0.5); // set to gray in else branch - } - // Modify the color after the if statement - color = color * 0.5; // Should result in 0.5 * 1.0 = 0.5 (gray) - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); - myp5.noStroke(); - myp5.shader(testShader); - myp5.plane(myp5.width, myp5.height); - // Check that the center pixel is gray (white * 0.5 = gray) + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); // start with black + if (condition > 0.5) { + color = myp5.float(1.0); // set to white in if branch + } else { + color = myp5.float(0.5); // set to gray in else branch + } + // Modify the color after the if statement + color = color * 0.5; // Should result in 0.5 * 1.0 = 0.5 (gray) + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); + myp5.noStroke(); + myp5.shader(testShader); + myp5.plane(myp5.width, myp5.height); + // Check that the center pixel is gray (white * 0.5 = gray) const pixelColor = myp5.get(25, 25); assert.approximately(pixelColor[0], 127, 5); // Red channel should be ~127 (gray) assert.approximately(pixelColor[1], 127, 5); // Green channel should be ~127 @@ -1098,24 +1188,27 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle modifications after if statement in both branches', () => { myp5.createCanvas(100, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const uv = inputs.texCoord; - const condition = uv.x > 0.5; // left half false, right half true - let color = myp5.float(0.0); - if (condition) { - color = myp5.float(1.0); // white on right side - } else { - color = myp5.float(0.8); // light gray on left side - } - // Multiply by 0.5 after the if statement - color = color * 0.5; - // Right side: 1.0 * 0.5 = 0.5 (medium gray) - // Left side: 0.8 * 0.5 = 0.4 (darker gray) - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const uv = inputs.texCoord; + const condition = uv.x > 0.5; // left half false, right half true + let color = myp5.float(0.0); + if (condition) { + color = myp5.float(1.0); // white on right side + } else { + color = myp5.float(0.8); // light gray on left side + } + // Multiply by 0.5 after the if statement + color = color * 0.5; + // Right side: 1.0 * 0.5 = 0.5 (medium gray) + // Left side: 0.8 * 0.5 = 0.4 (darker gray) + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1128,21 +1221,24 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle if-else-if chains', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const value = myp5.uniformFloat(() => 0.5); // middle value - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); - if (value > 0.8) { - color = myp5.float(1.0); // white for high values - } else if (value > 0.3) { - color = myp5.float(0.5); // gray for medium values - } else { - color = myp5.float(0.0); // black for low values - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const value = myp5.uniformFloat(() => 0.5); // middle value + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); + if (value > 0.8) { + color = myp5.float(1.0); // white for high values + } else if (value > 0.3) { + color = myp5.float(0.5); // gray for medium values + } else { + color = myp5.float(0.0); // black for low values + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1154,21 +1250,24 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle if-else-if chains in the else branch', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const value = myp5.uniformFloat(() => 0.2); // middle value - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); - if (value > 0.8) { - color = myp5.float(1.0); // white for high values - } else if (value > 0.3) { - color = myp5.float(0.5); // gray for medium values - } else { - color = myp5.float(0.0); // black for low values - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const value = myp5.uniformFloat(() => 0.2); // middle value + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); + if (value > 0.8) { + color = myp5.float(1.0); // white for high values + } else if (value > 0.3) { + color = myp5.float(0.5); // gray for medium values + } else { + color = myp5.float(0.0); // black for low values + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1180,29 +1279,32 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle conditional assignment in if-else-if chains', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const val = myp5.uniformFloat(() => Math.PI * 8); - myp5.getPixelInputs(inputs => { - let shininess = 0 - let color = 0 - if (val > 5) { - const elevation = myp5.sin(val) - if (elevation > 0.4) { - shininess = 0; - } else if (elevation > 0.25) { - shininess = 30; + const testShader = myp5.baseMaterialShader().modify( + () => { + const val = myp5.uniformFloat(() => Math.PI * 8); + myp5.getPixelInputs(inputs => { + let shininess = 0; + let color = 0; + if (val > 5) { + const elevation = myp5.sin(val); + if (elevation > 0.4) { + shininess = 0; + } else if (elevation > 0.25) { + shininess = 30; + } else { + color = 1; + shininess = 100; + } } else { - color = 1; - shininess = 100; + shininess += 25; } - } else { - shininess += 25; - } - inputs.shininess = shininess; - inputs.color = [color, color, color, 1]; - return inputs; - }); - }, { myp5 }); + inputs.shininess = shininess; + inputs.color = [color, color, color, 1]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1214,24 +1316,27 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle nested if statements', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const outerCondition = myp5.uniformFloat(() => 1.0); // true - const innerCondition = myp5.uniformFloat(() => 1.0); // true - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); - if (outerCondition > 0.5) { - if (innerCondition > 0.5) { - color = myp5.float(1.0); // white for both conditions true + const testShader = myp5.baseMaterialShader().modify( + () => { + const outerCondition = myp5.uniformFloat(() => 1.0); // true + const innerCondition = myp5.uniformFloat(() => 1.0); // true + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); + if (outerCondition > 0.5) { + if (innerCondition > 0.5) { + color = myp5.float(1.0); // white for both conditions true + } else { + color = myp5.float(0.5); // gray for outer true, inner false + } } else { - color = myp5.float(0.5); // gray for outer true, inner false + color = myp5.float(0.0); // black for outer false } - } else { - color = myp5.float(0.0); // black for outer false - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1243,16 +1348,22 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle if statement with || (OR) operator', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let c = [1, 1, 1, 1]; - if (myp5.abs(inputs.texCoord.x - 0.5) > 0.2 || myp5.abs(inputs.texCoord.y - 0.5) > 0.2) { - c = [1, 0, 0, 1]; - } - inputs.color = c; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let c = [1, 1, 1, 1]; + if ( + myp5.abs(inputs.texCoord.x - 0.5) > 0.2 || + myp5.abs(inputs.texCoord.y - 0.5) > 0.2 + ) { + c = [1, 0, 0, 1]; + } + inputs.color = c; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1271,16 +1382,19 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle if statement with && (AND) operator', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); - if (inputs.texCoord.x > 0.5 && inputs.texCoord.y > 0.5) { - color = myp5.float(1.0); - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); + if (inputs.texCoord.x > 0.5 && inputs.texCoord.y > 0.5) { + color = myp5.float(1.0); + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1303,16 +1417,19 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle struct property assignment in if-else branches', () => { myp5.createCanvas(100, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - if (inputs.texCoord.x > 0.5) { - inputs.color = [1, 0, 0, 1]; - } else { - inputs.color = [0, 0, 1, 1]; - } - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + if (inputs.texCoord.x > 0.5) { + inputs.color = [1, 0, 0, 1]; + } else { + inputs.color = [0, 0, 1, 1]; + } + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1331,25 +1448,27 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca // Keep one direct API test for completeness test('handle direct StrandsIf API usage', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const conditionValue = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.5); // initial gray - const assignments = myp5.strandsIf( - conditionValue.greaterThan(0), - () => { - let tmp = color.copy(); - tmp = myp5.float(1.0); // set to white in if branch - return { color: tmp }; - } - ).Else(() => { - return { color: color }; // keep original in else branch + const testShader = myp5.baseMaterialShader().modify( + () => { + const conditionValue = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.5); // initial gray + const assignments = myp5 + .strandsIf(conditionValue.greaterThan(0), () => { + let tmp = color.copy(); + tmp = myp5.float(1.0); // set to white in if branch + return { color: tmp }; + }) + .Else(() => { + return { color: color }; // keep original in else branch + }); + color = assignments.color; + inputs.color = [color, color, color, 1.0]; + return inputs; }); - color = assignments.color; - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1361,34 +1480,34 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('handle direct StrandsIf ElseIf API usage', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const value = myp5.uniformFloat(() => 0.5); // middle value - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); // initial black - const assignments = myp5.strandsIf( - value.greaterThan(0.8), - () => { - let tmp = color.copy(); - tmp = myp5.float(1.0); // white for high values - return { color: tmp }; - } - ).ElseIf( - value.greaterThan(0.3), - () => { - let tmp = color.copy(); - tmp = myp5.float(0.5); // gray for medium values - return { color: tmp }; - } - ).Else(() => { - let tmp = color.copy(); - tmp = myp5.float(0.0); // black for low values - return { color: tmp }; + const testShader = myp5.baseMaterialShader().modify( + () => { + const value = myp5.uniformFloat(() => 0.5); // middle value + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); // initial black + const assignments = myp5 + .strandsIf(value.greaterThan(0.8), () => { + let tmp = color.copy(); + tmp = myp5.float(1.0); // white for high values + return { color: tmp }; + }) + .ElseIf(value.greaterThan(0.3), () => { + let tmp = color.copy(); + tmp = myp5.float(0.5); // gray for medium values + return { color: tmp }; + }) + .Else(() => { + let tmp = color.copy(); + tmp = myp5.float(0.0); // black for low values + return { color: tmp }; + }); + color = assignments.color; + inputs.color = [color, color, color, 1.0]; + return inputs; }); - color = assignments.color; - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1401,24 +1520,31 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle comma-separated expressions with assignments', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let red = myp5.float(0.0); - let green = myp5.float(0.0); - let blue = myp5.float(0.0); - - if (condition > 0.5) { - // Use comma-separated expressions with assignments - red = myp5.float(1.0), green = myp5.float(0.5), blue = myp5.float(0.2); - } else { - red = myp5.float(0.0), green = myp5.float(0.0), blue = myp5.float(0.0); - } + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let red = myp5.float(0.0); + let green = myp5.float(0.0); + let blue = myp5.float(0.0); + + if (condition > 0.5) { + // Use comma-separated expressions with assignments + ((red = myp5.float(1.0)), + (green = myp5.float(0.5)), + (blue = myp5.float(0.2))); + } else { + ((red = myp5.float(0.0)), + (green = myp5.float(0.0)), + (blue = myp5.float(0.0))); + } - inputs.color = [red, green, blue, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [red, green, blue, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1433,16 +1559,19 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle early return in if-else branches', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor((inputs, canvasContent) => { - let value = 1; - if (value > 0.5) { - return [1, 0, 0, 1]; - } else { - return [0, 1, 0, 1]; - } - }); - }, { myp5 }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor((inputs, canvasContent) => { + let value = 1; + if (value > 0.5) { + return [1, 0, 0, 1]; + } else { + return [0, 1, 0, 1]; + } + }); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -1456,18 +1585,21 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle early return in if with content afterwards', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor((inputs, canvasContent) => { - let value = 1; - if (value > 0.5) { - return [1, 0, 0, 1]; - } + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor((inputs, canvasContent) => { + let value = 1; + if (value > 0.5) { + return [1, 0, 0, 1]; + } - let otherValue = 0.2; - otherValue *= 2; - return [otherValue, 0, 0, 1]; - }); - }, { myp5 }); + let otherValue = 0.2; + otherValue *= 2; + return [otherValue, 0, 0, 1]; + }); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -1481,18 +1613,21 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle false early return in if with content afterwards', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor((inputs, canvasContent) => { - let value = 1; - if (value < 0.5) { - return [1, 0, 0, 1]; - } + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor((inputs, canvasContent) => { + let value = 1; + if (value < 0.5) { + return [1, 0, 0, 1]; + } - let otherValue = 0.2; - otherValue *= 2; - return [otherValue, 0, 0, 1]; - }); - }, { myp5 }); + let otherValue = 0.2; + otherValue *= 2; + return [otherValue, 0, 0, 1]; + }); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -1506,22 +1641,25 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('using boolean intermediate variables', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor((inputs, canvasContent) => { - let value = 1; - let condition = 1 > 2; + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor((inputs, canvasContent) => { + let value = 1; + let condition = 1 > 2; - if (value < 0.5) { - condition = 0.5 < 2; - } + if (value < 0.5) { + condition = 0.5 < 2; + } - if (condition) { - return [1, 0, 0, 1] - } + if (condition) { + return [1, 0, 0, 1]; + } - return [0.4, 0, 0, 1]; - }); - }, { myp5 }); + return [0.4, 0, 0, 1]; + }); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -1535,23 +1673,26 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('using boolean intermediate variables in functions', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - const conditionMet = () => { - let condition = 1 > 2; - let value = 1; - if (value < 0.5) { - condition = 0.5 < 2; - } - return !condition - } - myp5.getColor((inputs, canvasContent) => { - if (conditionMet()) { - return [1, 0, 0, 1] - } + const testShader = myp5.baseFilterShader().modify( + () => { + const conditionMet = () => { + let condition = 1 > 2; + let value = 1; + if (value < 0.5) { + condition = 0.5 < 2; + } + return !condition; + }; + myp5.getColor((inputs, canvasContent) => { + if (conditionMet()) { + return [1, 0, 0, 1]; + } - return [0.4, 0, 0, 1]; - }); - }, { myp5 }); + return [0.4, 0, 0, 1]; + }); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -1565,23 +1706,26 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('using boolean intermediate variables in functions with early returns', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - const conditionMet = () => { - let value = 1; - if (value < 0.5) { - return true - } - return false - } - myp5.getColor((inputs, canvasContent) => { - if (conditionMet()) { - return [1, 0, 0, 1] - } + const testShader = myp5.baseFilterShader().modify( + () => { + const conditionMet = () => { + let value = 1; + if (value < 0.5) { + return true; + } + return false; + }; + myp5.getColor((inputs, canvasContent) => { + if (conditionMet()) { + return [1, 0, 0, 1]; + } - return [0.4, 0, 0, 1]; - }); - }, { myp5 }); - console.log(testShader.fragSrc()) + return [0.4, 0, 0, 1]; + }); + }, + { myp5 } + ); + console.log(testShader.fragSrc()); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -1596,12 +1740,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca suite('ternary expressions', () => { test('ternary changes color based on left/right side of canvas', () => { myp5.createCanvas(50, 25, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - inputs.color = inputs.texCoord.x > 0.5 ? [1, 0, 0, 1] : [0, 0, 1, 1]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + inputs.color = + inputs.texCoord.x > 0.5 ? [1, 0, 0, 1] : [0, 0, 1, 1]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1619,13 +1767,16 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('ternary with scalar values', () => { myp5.createCanvas(50, 25, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const brightness = inputs.texCoord.x > 0.5 ? 1.0 : 0.0; - inputs.color = [brightness, brightness, brightness, 1]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const brightness = inputs.texCoord.x > 0.5 ? 1.0 : 0.0; + inputs.color = [brightness, brightness, brightness, 1]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -1646,18 +1797,21 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle simple for loop with known iteration count', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); - for (let i = 0; i < 3; i++) { - color = color + 0.1; - } + for (let i = 0; i < 3; i++) { + color = color + 0.1; + } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1673,18 +1827,21 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle swizzle assignments in loops', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let color = [0, 0, 0, 1]; + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let color = [0, 0, 0, 1]; - for (let i = 0; i < 3; i++) { - color.rgb += 0.1; - } + for (let i = 0; i < 3; i++) { + color.rgb += 0.1; + } - inputs.color = color; - return inputs; - }); - }, { myp5 }); + inputs.color = color; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1700,20 +1857,23 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle for loop with variable as loop bound', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - const maxIterations = myp5.uniformInt(() => 2); + const testShader = myp5.baseMaterialShader().modify( + () => { + const maxIterations = myp5.uniformInt(() => 2); - myp5.getPixelInputs(inputs => { - let result = myp5.float(0.0); + myp5.getPixelInputs(inputs => { + let result = myp5.float(0.0); - for (let i = 0; i < maxIterations; i++) { - result = result + 0.25; - } + for (let i = 0; i < maxIterations; i++) { + result = result + 0.25; + } - inputs.color = [result, result, result, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [result, result, result, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1729,20 +1889,23 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle for loop modifying multiple variables', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let red = myp5.float(0.0); - let green = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let red = myp5.float(0.0); + let green = myp5.float(0.0); - for (let i = 0; i < 4; i++) { - red = red + 0.125; // 4 * 0.125 = 0.5 - green = green + 0.25; // 4 * 0.25 = 1.0 - } + for (let i = 0; i < 4; i++) { + red = red + 0.125; // 4 * 0.125 = 0.5 + green = green + 0.25; // 4 * 0.25 = 1.0 + } - inputs.color = [red, green, 0.0, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [red, green, 0.0, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1751,27 +1914,32 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca const pixelColor = myp5.get(25, 25); assert.approximately(pixelColor[0], 127, 5); // 0.5 * 255 ≈ 127 assert.approximately(pixelColor[1], 255, 5); // 1.0 * 255 = 255 - assert.approximately(pixelColor[2], 0, 5); // 0.0 * 255 = 0 + assert.approximately(pixelColor[2], 0, 5); // 0.0 * 255 = 0 }); test('handle for loop modifying multiple variables after minification', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let red = myp5.float(0.0); - let green = myp5.float(0.0); - - for (let i = 0; i < 4; i++) { - // Note the comma! - red = red + 0.125, // 4 * 0.125 = 0.5 - green = green + 0.25; // 4 * 0.25 = 1.0 - } + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let red = myp5.float(0.0); + let green = myp5.float(0.0); + + for (let i = 0; i < 4; i++) { + // Note the comma! + ((red = + red + + 0.125), // 4 * 0.125 = 0.5 + (green = green + 0.25)); // 4 * 0.25 = 1.0 + } - inputs.color = [red, green, 0.0, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [red, green, 0.0, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1780,26 +1948,29 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca const pixelColor = myp5.get(25, 25); assert.approximately(pixelColor[0], 127, 5); // 0.5 * 255 ≈ 127 assert.approximately(pixelColor[1], 255, 5); // 1.0 * 255 = 255 - assert.approximately(pixelColor[2], 0, 5); // 0.0 * 255 = 0 + assert.approximately(pixelColor[2], 0, 5); // 0.0 * 255 = 0 }); test('handle for loop with conditional inside', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let sum = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let sum = myp5.float(0.0); - for (let i = 0; i < 5; i++) { - if (i % 2 === 0) { - sum = sum + 0.1; // Add on even iterations: 0, 2, 4 + for (let i = 0; i < 5; i++) { + if (i % 2 === 0) { + sum = sum + 0.1; // Add on even iterations: 0, 2, 4 + } } - } - inputs.color = [sum, sum, sum, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [sum, sum, sum, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1815,20 +1986,23 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle nested for loops', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let total = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let total = myp5.float(0.0); - for (let i = 0; i < 2; i++) { - for (let j = 0; j < 3; j++) { - total = total + 0.05; // 2 * 3 = 6 iterations + for (let i = 0; i < 2; i++) { + for (let j = 0; j < 3; j++) { + total = total + 0.05; // 2 * 3 = 6 iterations + } } - } - inputs.color = [total, total, total, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [total, total, total, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1844,35 +2018,38 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle complex nested for loops with multiple phi assignments', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let outerSum = myp5.float(0.0); - let globalCounter = myp5.float(0.0); - - // Outer for loop modifying multiple variables - for (let i = 0; i < 2; i++) { - let innerSum = myp5.float(0.0); - let localCounter = myp5.float(0.0); - - // Inner for loop also modifying multiple variables - for (let j = 0; j < 2; j++) { - innerSum = innerSum + 0.1; - localCounter = localCounter + 1.0; - globalCounter = globalCounter + 0.5; // This modifies outer scope - } + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let outerSum = myp5.float(0.0); + let globalCounter = myp5.float(0.0); + + // Outer for loop modifying multiple variables + for (let i = 0; i < 2; i++) { + let innerSum = myp5.float(0.0); + let localCounter = myp5.float(0.0); + + // Inner for loop also modifying multiple variables + for (let j = 0; j < 2; j++) { + innerSum = innerSum + 0.1; + localCounter = localCounter + 1.0; + globalCounter = globalCounter + 0.5; // This modifies outer scope + } - // Complex state modification between loops involving all variables - innerSum = innerSum * localCounter; // 0.2 * 2.0 = 0.4 - outerSum = outerSum + innerSum; // Add to outer sum - globalCounter = globalCounter * 0.5; // Modify global again - } + // Complex state modification between loops involving all variables + innerSum = innerSum * localCounter; // 0.2 * 2.0 = 0.4 + outerSum = outerSum + innerSum; // Add to outer sum + globalCounter = globalCounter * 0.5; // Modify global again + } - // Final result should be: 2 iterations * 0.4 = 0.8 for outerSum - // globalCounter: ((0 + 2*0.5)*0.5 + 2*0.5)*0.5 = ((1)*0.5 + 1)*0.5 = 1.5*0.5 = 0.75 - inputs.color = [outerSum, globalCounter, 0.0, 1.0]; - return inputs; - }); - }, { myp5 }); + // Final result should be: 2 iterations * 0.4 = 0.8 for outerSum + // globalCounter: ((0 + 2*0.5)*0.5 + 2*0.5)*0.5 = ((1)*0.5 + 1)*0.5 = 1.5*0.5 = 0.75 + inputs.color = [outerSum, globalCounter, 0.0, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1887,28 +2064,31 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle nested for loops with state modification between loops', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let total = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let total = myp5.float(0.0); - // Outer for loop - for (let i = 0; i < 2; i++) { - let innerSum = myp5.float(0.0); + // Outer for loop + for (let i = 0; i < 2; i++) { + let innerSum = myp5.float(0.0); - // Inner for loop - for (let j = 0; j < 3; j++) { - innerSum = innerSum + 0.1; // 3 * 0.1 = 0.3 per outer iteration - } + // Inner for loop + for (let j = 0; j < 3; j++) { + innerSum = innerSum + 0.1; // 3 * 0.1 = 0.3 per outer iteration + } - // State modification between inner and outer loop - innerSum = innerSum * 0.5; // Multiply by 0.5: 0.3 * 0.5 = 0.15 - total = total + innerSum; // Add to total: 2 * 0.15 = 0.3 - } + // State modification between inner and outer loop + innerSum = innerSum * 0.5; // Multiply by 0.5: 0.3 * 0.5 = 0.15 + total = total + innerSum; // Add to total: 2 * 0.15 = 0.3 + } - inputs.color = [total, total, total, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [total, total, total, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1924,18 +2104,21 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle for loop using loop variable in calculations', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let sum = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let sum = myp5.float(0.0); - for (let i = 1; i <= 3; i++) { - sum = sum + (i * 0.1); // 1*0.1 + 2*0.1 + 3*0.1 = 0.6 - } + for (let i = 1; i <= 3; i++) { + sum = sum + i * 0.1; // 1*0.1 + 2*0.1 + 3*0.1 = 0.6 + } - inputs.color = [sum, sum, sum, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [sum, sum, sum, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1952,27 +2135,30 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle direct StrandsFor API usage', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let accumulator = myp5.float(0.0); - - const loopResult = myp5.strandsFor( - () => 0, - (loopVar) => loopVar < 4, - (loopVar) => loopVar + 1, - (loopVar, vars) => { - let newValue = vars.accumulator.copy(); - newValue = newValue + 0.125; - return { accumulator: newValue }; - }, - { accumulator: accumulator.copy() }, - ); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let accumulator = myp5.float(0.0); + + const loopResult = myp5.strandsFor( + () => 0, + loopVar => loopVar < 4, + loopVar => loopVar + 1, + (loopVar, vars) => { + let newValue = vars.accumulator.copy(); + newValue = newValue + 0.125; + return { accumulator: newValue }; + }, + { accumulator: accumulator.copy() } + ); - accumulator = loopResult.accumulator; - inputs.color = [accumulator, accumulator, accumulator, 1.0]; - return inputs; - }); - }, { myp5 }); + accumulator = loopResult.accumulator; + inputs.color = [accumulator, accumulator, accumulator, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -1988,22 +2174,25 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle for loop with break statement', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let color = 0; - let maxIterations = 5; + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let color = 0; + let maxIterations = 5; - for (let i = 0; i < 100; i++) { - if (i >= maxIterations) { - break; + for (let i = 0; i < 100; i++) { + if (i >= maxIterations) { + break; + } + color = color + 0.1; } - color = color + 0.1; - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -2017,25 +2206,28 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle statements after for loop before return', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let avg = myp5.vec3(0.0); - let total = 0.0; - - // Simulate blur-like accumulation in for loop - for (let i = 0; i < 3; i++) { - const sample = myp5.vec3(0.2, 0.1, 0.3); - const weight = 1.0; - avg += weight * sample; - total += weight; - } + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let avg = myp5.vec3(0.0); + let total = 0.0; + + // Simulate blur-like accumulation in for loop + for (let i = 0; i < 3; i++) { + const sample = myp5.vec3(0.2, 0.1, 0.3); + const weight = 1.0; + avg += weight * sample; + total += weight; + } - const blended = avg / total; + const blended = avg / total; - inputs.color = [blended.x, blended.y, blended.z, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [blended.x, blended.y, blended.z, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -2043,30 +2235,33 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca // Expected result: (3 * [0.2, 0.1, 0.3]) / 3 = [0.2, 0.1, 0.3] const pixelColor = myp5.get(25, 25); - assert.approximately(pixelColor[0], 51, 5); // 0.2 * 255 ≈ 51 - assert.approximately(pixelColor[1], 25, 5); // 0.1 * 255 ≈ 25 - assert.approximately(pixelColor[2], 77, 5); // 0.3 * 255 ≈ 77 + assert.approximately(pixelColor[0], 51, 5); // 0.2 * 255 ≈ 51 + assert.approximately(pixelColor[1], 25, 5); // 0.1 * 255 ≈ 25 + assert.approximately(pixelColor[2], 77, 5); // 0.3 * 255 ≈ 77 }); test('handle nested loops with accumulator modified in inner loop', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor((inputs, canvasContent) => { - let aliveNeighbours = 0; + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor((inputs, canvasContent) => { + let aliveNeighbours = 0; - for (let xOff = -1; xOff <= 1; xOff++) { - for (let yOff = -1; yOff <= 1; yOff++) { - if (xOff != 0 || yOff != 0) { - aliveNeighbours += 0.1; + for (let xOff = -1; xOff <= 1; xOff++) { + for (let yOff = -1; yOff <= 1; yOff++) { + if (xOff != 0 || yOff != 0) { + aliveNeighbours += 0.1; + } } } - } - // 8 neighbors (all except center): 8 * 0.1 = 0.8 - return [aliveNeighbours, aliveNeighbours, aliveNeighbours, 1]; - }); - }, { myp5 }); + // 8 neighbors (all except center): 8 * 0.1 = 0.8 + return [aliveNeighbours, aliveNeighbours, aliveNeighbours, 1]; + }); + }, + { myp5 } + ); myp5.background(255, 0, 0); // Red background myp5.filter(testShader); @@ -2084,16 +2279,19 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca myp5.createCanvas(50, 50, myp5.WEBGL); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let worldPos = myp5.varyingVec3(); - myp5.getWorldInputs((inputs) => { - worldPos = inputs.position.xyz; - return inputs; - }); - myp5.getFinalColor((c) => { - return [myp5.abs(worldPos / 25), 1]; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + let worldPos = myp5.varyingVec3(); + myp5.getWorldInputs(inputs => { + worldPos = inputs.position.xyz; + return inputs; + }); + myp5.getFinalColor(c => { + return [myp5.abs(worldPos / 25), 1]; + }); + }, + { myp5 } + ); myp5.background(0, 0, 255); // Make the background blue to tell it apart myp5.noStroke(); @@ -2117,16 +2315,19 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca myp5.createCanvas(50, 50, myp5.WEBGL); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let worldPos = myp5.varyingVec3(); - myp5.getWorldInputs((inputs) => { - worldPos.xyz = inputs.position.xyz; - return inputs; - }); - myp5.getFinalColor((c) => { - return [myp5.abs(worldPos / 25), 1]; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + let worldPos = myp5.varyingVec3(); + myp5.getWorldInputs(inputs => { + worldPos.xyz = inputs.position.xyz; + return inputs; + }); + myp5.getFinalColor(c => { + return [myp5.abs(worldPos / 25), 1]; + }); + }, + { myp5 } + ); myp5.background(0, 0, 255); // Make the background blue to tell it apart myp5.noStroke(); @@ -2150,17 +2351,20 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca myp5.createCanvas(50, 50, myp5.WEBGL); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let worldPos = myp5.varyingVec3(); - myp5.getWorldInputs((inputs) => { - worldPos = inputs.position.xyz; - inputs.position.xyz = worldPos + [25, 25, 0]; - return inputs; - }); - myp5.getFinalColor((c) => { - return [myp5.abs(worldPos / 25), 1]; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + let worldPos = myp5.varyingVec3(); + myp5.getWorldInputs(inputs => { + worldPos = inputs.position.xyz; + inputs.position.xyz = worldPos + [25, 25, 0]; + return inputs; + }); + myp5.getFinalColor(c => { + return [myp5.abs(worldPos / 25), 1]; + }); + }, + { myp5 } + ); myp5.background(0, 0, 255); // Make the background blue to tell it apart myp5.noStroke(); @@ -2184,17 +2388,20 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca myp5.createCanvas(50, 50, myp5.WEBGL); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let processedNormal = myp5.sharedVec3(); - myp5.getPixelInputs((inputs) => { - processedNormal = myp5.normalize(inputs.normal); - return inputs; - }); - myp5.getFinalColor((c) => { - // Use the processed normal to create a color - should be [0, 0, 1] for plane facing camera - return [myp5.abs(processedNormal), 1]; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + let processedNormal = myp5.sharedVec3(); + myp5.getPixelInputs(inputs => { + processedNormal = myp5.normalize(inputs.normal); + return inputs; + }); + myp5.getFinalColor(c => { + // Use the processed normal to create a color - should be [0, 0, 1] for plane facing camera + return [myp5.abs(processedNormal), 1]; + }); + }, + { myp5 } + ); myp5.background(255, 0, 0); // Red background to distinguish from result myp5.noStroke(); @@ -2203,8 +2410,8 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca // Normal of plane facing camera should be [0, 0, 1], so color should be [0, 0, 255] const centerColor = myp5.get(25, 25); - assert.approximately(centerColor[0], 0, 5); // Red component - assert.approximately(centerColor[1], 0, 5); // Green component + assert.approximately(centerColor[0], 0, 5); // Red component + assert.approximately(centerColor[1], 0, 5); // Green component assert.approximately(centerColor[2], 255, 5); // Blue component }); @@ -2212,21 +2419,24 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca myp5.createCanvas(50, 50, myp5.WEBGL); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let processedNormal = myp5.sharedVec3(); - myp5.objectInputs.begin(); - myp5.objectInputs.position += [0, 0, 0]; - myp5.objectInputs.end(); + const testShader = myp5.baseMaterialShader().modify( + () => { + let processedNormal = myp5.sharedVec3(); + myp5.objectInputs.begin(); + myp5.objectInputs.position += [0, 0, 0]; + myp5.objectInputs.end(); - myp5.pixelInputs.begin(); - processedNormal = myp5.normalize(myp5.pixelInputs.normal); - myp5.pixelInputs.end(); + myp5.pixelInputs.begin(); + processedNormal = myp5.normalize(myp5.pixelInputs.normal); + myp5.pixelInputs.end(); - myp5.finalColor.begin(); - // Use the processed normal to create a color - should be [0, 0, 1] for plane facing camera - myp5.finalColor.set([myp5.abs(processedNormal), 1]); - myp5.finalColor.end(); - }, { myp5 }); + myp5.finalColor.begin(); + // Use the processed normal to create a color - should be [0, 0, 1] for plane facing camera + myp5.finalColor.set([myp5.abs(processedNormal), 1]); + myp5.finalColor.end(); + }, + { myp5 } + ); myp5.background(255, 0, 0); // Red background to distinguish from result myp5.noStroke(); @@ -2235,8 +2445,8 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca // Normal of plane facing camera should be [0, 0, 1], so color should be [0, 0, 255] const centerColor = myp5.get(25, 25); - assert.approximately(centerColor[0], 0, 5); // Red component - assert.approximately(centerColor[1], 0, 5); // Green component + assert.approximately(centerColor[0], 0, 5); // Red component + assert.approximately(centerColor[1], 0, 5); // Green component assert.approximately(centerColor[2], 255, 5); // Blue component }); @@ -2244,16 +2454,19 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca myp5.createCanvas(50, 50, myp5.WEBGL); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let worldPos = myp5.sharedVec3(); - myp5.getWorldInputs((inputs) => { - worldPos = inputs.position.xyz; - return inputs; - }); - myp5.getFinalColor((c) => { - return [myp5.abs(worldPos / 25), 1]; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + let worldPos = myp5.sharedVec3(); + myp5.getWorldInputs(inputs => { + worldPos = inputs.position.xyz; + return inputs; + }); + myp5.getFinalColor(c => { + return [myp5.abs(worldPos / 25), 1]; + }); + }, + { myp5 } + ); myp5.background(0, 0, 255); // Make the background blue to tell it apart myp5.noStroke(); @@ -2278,12 +2491,15 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle getColor hook with non-struct return type', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor((inputs, canvasContent) => { - // Simple test - just return a constant color - return [1.0, 0.5, 0.0, 1.0]; // Orange color - }); - }, { myp5 }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor((inputs, canvasContent) => { + // Simple test - just return a constant color + return [1.0, 0.5, 0.0, 1.0]; // Orange color + }); + }, + { myp5 } + ); // Create a simple scene to filter myp5.background(0, 0, 255); // Blue background @@ -2295,43 +2511,54 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca const pixelColor = myp5.get(25, 25); assert.approximately(pixelColor[0], 255, 5); // Red channel should be 255 assert.approximately(pixelColor[1], 127, 5); // Green channel should be ~127 - assert.approximately(pixelColor[2], 0, 5); // Blue channel should be 0 + assert.approximately(pixelColor[2], 0, 5); // Blue channel should be 0 }); test('simple vector multiplication in filter shader', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor((inputs, canvasContent) => { - // Test simple scalar * vector operation - const scalar = 0.5; - const vector = [1, 2]; - const result = scalar * vector; - return [result.x, result.y, 0, 1]; - }); - }, { myp5 }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor((inputs, canvasContent) => { + // Test simple scalar * vector operation + const scalar = 0.5; + const vector = [1, 2]; + const result = scalar * vector; + return [result.x, result.y, 0, 1]; + }); + }, + { myp5 } + ); }); test('handle complex filter shader with for loop and vector operations', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - const r = myp5.uniformFloat(() => 3); // Small value for testing - myp5.getColor((inputs, canvasContent) => { - let sum = [0, 0, 0, 0]; - let samples = 1; - - for (let i = 0; i < r; i++) { - samples++; - sum += myp5.texture(canvasContent, inputs.texCoord + (i / r) * [ - myp5.sin(4 * myp5.PI * i / r), - myp5.cos(4 * myp5.PI * i / r) - ]); - } + const testShader = myp5.baseFilterShader().modify( + () => { + const r = myp5.uniformFloat(() => 3); // Small value for testing + myp5.getColor((inputs, canvasContent) => { + let sum = [0, 0, 0, 0]; + let samples = 1; + + for (let i = 0; i < r; i++) { + samples++; + sum += myp5.texture( + canvasContent, + inputs.texCoord + + (i / r) * + [ + myp5.sin((4 * myp5.PI * i) / r), + myp5.cos((4 * myp5.PI * i) / r) + ] + ); + } - return sum / samples; - }); - }, { myp5 }); + return sum / samples; + }); + }, + { myp5 } + ); // Create a simple scene to filter myp5.background(255, 0, 0); // Red background @@ -2354,11 +2581,14 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca expect(() => { myp5.createCanvas(50, 50, myp5.WEBGL); const input = new Array(i).fill(10); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor(() => { - return [myp5.noise(input), 0, 0, 1]; - }); - }, { myp5, input }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor(() => { + return [myp5.noise(input), 0, 0, 1]; + }); + }, + { myp5, input } + ); myp5.shader(testShader); myp5.plane(10, 10); }).not.toThrowError(); @@ -2368,11 +2598,14 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca expect(() => { myp5.createCanvas(50, 50, myp5.WEBGL); const input = new Array(i).fill(10); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor(() => { - return [myp5.noise(...input), 0, 0, 1]; - }); - }, { myp5, input }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor(() => { + return [myp5.noise(...input), 0, 0, 1]; + }); + }, + { myp5, input } + ); myp5.shader(testShader); myp5.plane(10, 10); }).not.toThrowError(); @@ -2384,11 +2617,14 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca expect(() => { myp5.createCanvas(50, 50, myp5.WEBGL); const input = new Array(i).fill(10); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor(() => { - return [myp5.noise(input), 0, 0, 1]; - }); - }, { myp5, input }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor(() => { + return [myp5.noise(input), 0, 0, 1]; + }); + }, + { myp5, input } + ); myp5.shader(testShader); myp5.plane(10, 10); }).toThrowError(); @@ -2398,11 +2634,14 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca expect(() => { myp5.createCanvas(50, 50, myp5.WEBGL); const input = new Array(i).fill(10); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor(() => { - return [myp5.noise(...input), 0, 0, 1]; - }); - }, { myp5, input }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor(() => { + return [myp5.noise(...input), 0, 0, 1]; + }); + }, + { myp5, input } + ); myp5.shader(testShader); myp5.plane(10, 10); }).toThrowError(); @@ -2413,11 +2652,14 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('Can use begin/end API for hooks with result', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor.begin(); - myp5.getColor.set([1.0, 0.5, 0.0, 1.0]); - myp5.getColor.end(); - }, { myp5 }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor.begin(); + myp5.getColor.set([1.0, 0.5, 0.0, 1.0]); + myp5.getColor.end(); + }, + { myp5 } + ); // Create a simple scene to filter myp5.background(0, 0, 255); // Blue background @@ -2435,11 +2677,14 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('Can use begin/end API for hooks with hook alias', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.filterColor.begin(); - myp5.filterColor.set([1.0, 0.5, 0.0, 1.0]); - myp5.filterColor.end(); - }, { myp5 }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.filterColor.begin(); + myp5.filterColor.set([1.0, 0.5, 0.0, 1.0]); + myp5.filterColor.end(); + }, + { myp5 } + ); // Create a simple scene to filter myp5.background(0, 0, 255); // Blue background @@ -2457,18 +2702,21 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('Can use begin/end API for hooks modifying inputs', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs.begin(); - myp5.getPixelInputs.color = [1.0, 0.5, 0.0, 1.0]; - myp5.getPixelInputs.end(); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs.begin(); + myp5.getPixelInputs.color = [1.0, 0.5, 0.0, 1.0]; + myp5.getPixelInputs.end(); + }, + { myp5 } + ); // Create a simple scene to filter myp5.background(0, 0, 255); // Blue background // Draw a fullscreen rectangle myp5.noStroke(); - myp5.fill('red') + myp5.fill('red'); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -2482,13 +2730,19 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('Can use begin/end API for hooks with struct access', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.filterColor.begin(); - let c = myp5.getTexture(myp5.filterColor.canvasContent, myp5.filterColor.texCoord); - c.r = 1; - myp5.filterColor.set(c); - myp5.filterColor.end(); - }, { myp5 }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.filterColor.begin(); + let c = myp5.getTexture( + myp5.filterColor.canvasContent, + myp5.filterColor.texCoord + ); + c.r = 1; + myp5.filterColor.set(c); + myp5.filterColor.end(); + }, + { myp5 } + ); // Create a simple scene to filter myp5.background(0, 0, 255); // Blue background @@ -2506,18 +2760,21 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('Can use begin/end API for hooks with get* prefix removed', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.pixelInputs.begin(); - myp5.pixelInputs.color = [1.0, 0.5, 0.0, 1.0]; - myp5.pixelInputs.end(); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.pixelInputs.begin(); + myp5.pixelInputs.color = [1.0, 0.5, 0.0, 1.0]; + myp5.pixelInputs.end(); + }, + { myp5 } + ); // Create a simple scene to filter myp5.background(0, 0, 255); // Blue background // Draw a fullscreen rectangle myp5.noStroke(); - myp5.fill('red') + myp5.fill('red'); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -2531,16 +2788,19 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle .set() in if-else branches with flat API', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.filterColor.begin(); - let value = 1; - if (value > 0.5) { - myp5.filterColor.set([1, 0, 0, 1]); - } else { - myp5.filterColor.set([0, 1, 0, 1]); - } - myp5.filterColor.end(); - }, { myp5 }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.filterColor.begin(); + let value = 1; + if (value > 0.5) { + myp5.filterColor.set([1, 0, 0, 1]); + } else { + myp5.filterColor.set([0, 1, 0, 1]); + } + myp5.filterColor.end(); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -2554,15 +2814,18 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle .set() in for loop with flat API', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.filterColor.begin(); - for (let i = 0; i < 3; i++) { - if (i === 2) { - myp5.filterColor.set([i/2, 0, 0, 1]); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.filterColor.begin(); + for (let i = 0; i < 3; i++) { + if (i === 2) { + myp5.filterColor.set([i / 2, 0, 0, 1]); + } } - } - myp5.filterColor.end(); - }, { myp5 }); + myp5.filterColor.end(); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -2576,19 +2839,24 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle uniformFloat with control flow in callback', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - // Uniform callback with an if-statement and multiple return paths - const pastOneSecond = myp5.uniformFloat(() => { - if (myp5.frameCount > 1000) { - return 1; - } - return 0; - }); + const testShader = myp5.baseFilterShader().modify( + () => { + // Uniform callback with an if-statement and multiple return paths + const pastOneSecond = myp5.uniformFloat(() => { + if (myp5.frameCount > 1000) { + return 1; + } + return 0; + }); - myp5.filterColor.begin(); - myp5.filterColor.set(myp5.mix([1, 0, 0, 1], [0, 1, 0, 1], pastOneSecond)); - myp5.filterColor.end(); - }, { myp5 }); + myp5.filterColor.begin(); + myp5.filterColor.set( + myp5.mix([1, 0, 0, 1], [0, 1, 0, 1], pastOneSecond) + ); + myp5.filterColor.end(); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -2603,20 +2871,23 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle uniformFloat with for loop in callback', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - // Uniform callback with a for loop accumulating a value - const brightness = myp5.uniformFloat(() => { - let sum = 0; - for (let i = 0; i < 3; i++) { - sum += i; - } - return sum / 10; // 0+1+2=3, 3/10=0.3 - }); + const testShader = myp5.baseFilterShader().modify( + () => { + // Uniform callback with a for loop accumulating a value + const brightness = myp5.uniformFloat(() => { + let sum = 0; + for (let i = 0; i < 3; i++) { + sum += i; + } + return sum / 10; // 0+1+2=3, 3/10=0.3 + }); - myp5.filterColor.begin(); - myp5.filterColor.set([brightness, 0, 0, 1]); - myp5.filterColor.end(); - }, { myp5 }); + myp5.filterColor.begin(); + myp5.filterColor.set([brightness, 0, 0, 1]); + myp5.filterColor.end(); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -2631,17 +2902,20 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle uniformFloat with sub-function call in callback', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - // Uniform callback that calls a sub-function - const brightness = myp5.uniformFloat(() => { - const getValue = () => 0.6; - return getValue(); - }); + const testShader = myp5.baseFilterShader().modify( + () => { + // Uniform callback that calls a sub-function + const brightness = myp5.uniformFloat(() => { + const getValue = () => 0.6; + return getValue(); + }); - myp5.filterColor.begin(); - myp5.filterColor.set([brightness, 0, 0, 1]); - myp5.filterColor.end(); - }, { myp5 }); + myp5.filterColor.begin(); + myp5.filterColor.set([brightness, 0, 0, 1]); + myp5.filterColor.end(); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -2656,19 +2930,24 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle uniformFloat with control flow in non-inline callback', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - function pastOneSecondValue() { - if (myp5.frameCount > 1000) { - return 1; + const testShader = myp5.baseFilterShader().modify( + () => { + function pastOneSecondValue() { + if (myp5.frameCount > 1000) { + return 1; + } + return 0; } - return 0; - } - const pastOneSecond = myp5.uniformFloat(pastOneSecondValue); + const pastOneSecond = myp5.uniformFloat(pastOneSecondValue); - myp5.filterColor.begin(); - myp5.filterColor.set(myp5.mix([1, 0, 0, 1], [0, 1, 0, 1], pastOneSecond)); - myp5.filterColor.end(); - }, { myp5 }); + myp5.filterColor.begin(); + myp5.filterColor.set( + myp5.mix([1, 0, 0, 1], [0, 1, 0, 1], pastOneSecond) + ); + myp5.filterColor.end(); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -2683,20 +2962,23 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle uniformFloat with for loop in non-inline callback', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - function brightnessValue() { - let sum = 0; - for (let i = 0; i < 3; i++) { - sum += i; + const testShader = myp5.baseFilterShader().modify( + () => { + function brightnessValue() { + let sum = 0; + for (let i = 0; i < 3; i++) { + sum += i; + } + return sum / 10; // 0+1+2=3, 3/10=0.3 } - return sum / 10; // 0+1+2=3, 3/10=0.3 - } - const brightness = myp5.uniformFloat(brightnessValue); + const brightness = myp5.uniformFloat(brightnessValue); - myp5.filterColor.begin(); - myp5.filterColor.set([brightness, 0, 0, 1]); - myp5.filterColor.end(); - }, { myp5 }); + myp5.filterColor.begin(); + myp5.filterColor.set([brightness, 0, 0, 1]); + myp5.filterColor.end(); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -2711,19 +2993,22 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle uniformFloat with sub-function call in non-inline callback', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - function getValue() { - return 0.6; - } - function brightnessValue() { - return getValue(); - } - const brightness = myp5.uniformFloat(brightnessValue); + const testShader = myp5.baseFilterShader().modify( + () => { + function getValue() { + return 0.6; + } + function brightnessValue() { + return getValue(); + } + const brightness = myp5.uniformFloat(brightnessValue); - myp5.filterColor.begin(); - myp5.filterColor.set([brightness, 0, 0, 1]); - myp5.filterColor.end(); - }, { myp5 }); + myp5.filterColor.begin(); + myp5.filterColor.set([brightness, 0, 0, 1]); + myp5.filterColor.end(); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -2738,18 +3023,21 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca test('handle false .set() in if with content afterwards with flat API', () => { myp5.createCanvas(50, 50, myp5.WEBGL); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.filterColor.begin(); - let value = 1; - if (value < 0.5) { - myp5.filterColor.set([1, 0, 0, 1]); - } + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.filterColor.begin(); + let value = 1; + if (value < 0.5) { + myp5.filterColor.set([1, 0, 0, 1]); + } - let otherValue = 0.2; - otherValue *= 2; - myp5.filterColor.set([otherValue, 0, 0, 1]); - myp5.filterColor.end(); - }, { myp5 }); + let otherValue = 0.2; + otherValue *= 2; + myp5.filterColor.set([otherValue, 0, 0, 1]); + myp5.filterColor.end(); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -2761,60 +3049,72 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); test('allows scalar broadcast when assigning a scalar to a sharedVec3 (bridge)', async () => { - await myp5.createCanvas(5, 5, myp5.WEBGL); + await myp5.createCanvas(5, 5, myp5.WEBGL); - expect(() => { - myp5.baseMaterialShader().modify(() => { - let worldPosX = myp5.sharedVec3(); - myp5.getWorldInputs(inputs => { - worldPosX = inputs.position.x; // scalar → vec3, valid broadcast - return inputs; - }); - },{myp5}); - }).not.toThrow(); -}); + expect(() => { + myp5.baseMaterialShader().modify( + () => { + let worldPosX = myp5.sharedVec3(); + myp5.getWorldInputs(inputs => { + worldPosX = inputs.position.x; // scalar → vec3, valid broadcast + return inputs; + }); + }, + { myp5 } + ); + }).not.toThrow(); + }); -test('reports a friendly error when assigning a vec2 to a sharedVec3 (bridge)', async () => { - await myp5.createCanvas(5, 5, myp5.WEBGL); + test('reports a friendly error when assigning a vec2 to a sharedVec3 (bridge)', async () => { + await myp5.createCanvas(5, 5, myp5.WEBGL); - expect(() => { - myp5.baseMaterialShader().modify(() => { - let myVec = myp5.sharedVec3(); - myp5.getWorldInputs(inputs => { - myVec = inputs.position.xy; // vec2 → vec3 mismatch - return inputs; - }); - },{myp5}); - }).toThrow(/dimension mismatch/); -}); + expect(() => { + myp5.baseMaterialShader().modify( + () => { + let myVec = myp5.sharedVec3(); + myp5.getWorldInputs(inputs => { + myVec = inputs.position.xy; // vec2 → vec3 mismatch + return inputs; + }); + }, + { myp5 } + ); + }).toThrow(/dimension mismatch/); + }); -test('reports a friendly error on dimension mismatch via swizzle write (bridgeSwizzle)', async () => { - await myp5.createCanvas(5, 5, myp5.WEBGL); + test('reports a friendly error on dimension mismatch via swizzle write (bridgeSwizzle)', async () => { + await myp5.createCanvas(5, 5, myp5.WEBGL); - expect(() => { - myp5.baseMaterialShader().modify(() => { - let myVec = myp5.sharedVec3(); - myp5.getWorldInputs(inputs => { - myVec.xy = inputs.position; // vec3 → 2-component swizzle mismatch - return inputs; - }); - },{myp5}); - }).toThrow(/dimension mismatch/); -}); + expect(() => { + myp5.baseMaterialShader().modify( + () => { + let myVec = myp5.sharedVec3(); + myp5.getWorldInputs(inputs => { + myVec.xy = inputs.position; // vec3 → 2-component swizzle mismatch + return inputs; + }); + }, + { myp5 } + ); + }).toThrow(/dimension mismatch/); + }); -test('does not error when shared variable assignment dimensions match', async () => { - await myp5.createCanvas(5, 5, myp5.WEBGL); + test('does not error when shared variable assignment dimensions match', async () => { + await myp5.createCanvas(5, 5, myp5.WEBGL); - expect(() => { - myp5.baseMaterialShader().modify(() => { - let myVec = myp5.sharedVec3(); - myp5.getWorldInputs(inputs => { - myVec = inputs.position; // vec3 → vec3, OK - return inputs; - }); - },{myp5}); - }).not.toThrow(); -}); + expect(() => { + myp5.baseMaterialShader().modify( + () => { + let myVec = myp5.sharedVec3(); + myp5.getWorldInputs(inputs => { + myVec = inputs.position; // vec3 → vec3, OK + return inputs; + }); + }, + { myp5 } + ); + }).not.toThrow(); + }); }); suite('p5.strands error messages', () => { @@ -2840,12 +3140,21 @@ test('does not error when shared variable assignment dimensions match', async () myp5.createCanvas(50, 50, myp5.WEBGL); try { - myp5.baseMaterialShader().modify(() => { - myp5.getWorldInputs(() => [1, 2, 3, 4]); // vec4 instead of Vertex struct - }, { myp5 }); - } catch (e) { /* expected */ } + myp5.baseMaterialShader().modify( + () => { + myp5.getWorldInputs(() => [1, 2, 3, 4]); // vec4 instead of Vertex struct + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } - assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called'); + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called' + ); const errMsg = mockUserError.mock.calls[0][1]; assert.notInclude(errMsg, 'a undefined'); // assert.include(errMsg, 'float4'); @@ -2858,14 +3167,21 @@ test('does not error when shared variable assignment dimensions match', async () myp5.createCanvas(50, 50, myp5.WEBGL); try { - myp5.baseMaterialShader().modify(() => { - myp5.getFinalColor((c) => [c.r, c.g, c.b]); // vec3 instead of vec4 - }, { myp5 }); - } catch (e) { /* expected */ } - - + myp5.baseMaterialShader().modify( + () => { + myp5.getFinalColor(c => [c.r, c.g, c.b]); // vec3 instead of vec4 + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } - assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called, btw: '+globalThis.FESCalled); + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called, btw: ' + globalThis.FESCalled + ); const errMsg = mockUserError.mock.calls[0][1]; assert.include(errMsg, 'float3'); assert.include(errMsg, 'float4'); @@ -2875,14 +3191,23 @@ test('does not error when shared variable assignment dimensions match', async () myp5.createCanvas(50, 50, myp5.WEBGL); try { - myp5.baseMaterialShader().modify(() => { - myp5.getWorldInputs((inputs) => { - return { position: inputs.position }; - }); - }, { myp5 }); - } catch (e) { /* expected */ } + myp5.baseMaterialShader().modify( + () => { + myp5.getWorldInputs(inputs => { + return { position: inputs.position }; + }); + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } - assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called'); + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called' + ); const errMsg = mockUserError.mock.calls[0][1]; assert.include(errMsg, 'Expected properties'); assert.include(errMsg, 'Received properties'); @@ -2892,17 +3217,27 @@ test('does not error when shared variable assignment dimensions match', async () myp5.createCanvas(50, 50, myp5.WEBGL); try { - myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - // float1 vs float4 - type mismatch - const val = inputs.texCoord.x > 0.5 ? myp5.float(1.0) : [1, 0, 0, 1]; - inputs.color = [val, val, val, 1]; - return inputs; - }); - }, { myp5 }); - } catch (e) { /* expected */ } + myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + // float1 vs float4 - type mismatch + const val = + inputs.texCoord.x > 0.5 ? myp5.float(1.0) : [1, 0, 0, 1]; + inputs.color = [val, val, val, 1]; + return inputs; + }); + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } - assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called'); + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called' + ); const errMsg = mockUserError.mock.calls[0][1]; assert.include(errMsg, 'ternary'); assert.include(errMsg, 'float1'); @@ -2913,19 +3248,22 @@ test('does not error when shared variable assignment dimensions match', async () myp5.createCanvas(50, 50, myp5.WEBGL); expectLoopProtectionError(() => { - myp5.baseFilterShader().modify(() => { - myp5.filterColor.begin(); - { - loopProtect.protect({ line: 11, reset: true }); - for (let i = 0; i < 10; i++) { - if (loopProtect.protect({ line: 11 })) { - break; + myp5.baseFilterShader().modify( + () => { + myp5.filterColor.begin(); + { + loopProtect.protect({ line: 11, reset: true }); + for (let i = 0; i < 10; i++) { + if (loopProtect.protect({ line: 11 })) { + break; + } } } - } - myp5.filterColor.set([1, 0, 0, 1]); - myp5.filterColor.end(); - }, { myp5 }); + myp5.filterColor.set([1, 0, 0, 1]); + myp5.filterColor.end(); + }, + { myp5 } + ); }); }); @@ -2933,14 +3271,17 @@ test('does not error when shared variable assignment dimensions match', async () myp5.createCanvas(50, 50, myp5.WEBGL); expectLoopProtectionError(() => { - myp5.baseFilterShader().modify(() => { - myp5.filterColor.begin(); - for (let i = 0; i < 10; i++) { - window.$OP && $OP.loopProtect({ line: 11, ch: 2 }); - } - myp5.filterColor.set([1, 0, 0, 1]); - myp5.filterColor.end(); - }, { myp5 }); + myp5.baseFilterShader().modify( + () => { + myp5.filterColor.begin(); + for (let i = 0; i < 10; i++) { + window.$OP && $OP.loopProtect({ line: 11, ch: 2 }); + } + myp5.filterColor.set([1, 0, 0, 1]); + myp5.filterColor.end(); + }, + { myp5 } + ); }); }); @@ -2948,15 +3289,26 @@ test('does not error when shared variable assignment dimensions match', async () myp5.createCanvas(50, 50, myp5.WEBGL); try { - myp5.baseMaterialShader().modify(() => { - myp5.getWorldInputs.begin(); - myp5.getWorldInputs.end(); - const pos = myp5.getWorldInputs.position; - }, { myp5 }); - } catch (e) { /* expected */ } - - assert.isAbove(mockUserError.mock.calls.length, 0, 'FES.userError should have been called'); - const scopeCall = mockUserError.mock.calls.find(call => call[0] === 'scope error'); + myp5.baseMaterialShader().modify( + () => { + myp5.getWorldInputs.begin(); + myp5.getWorldInputs.end(); + const pos = myp5.getWorldInputs.position; + }, + { myp5 } + ); + } catch (e) { + /* expected */ + } + + assert.isAbove( + mockUserError.mock.calls.length, + 0, + 'FES.userError should have been called' + ); + const scopeCall = mockUserError.mock.calls.find( + call => call[0] === 'scope error' + ); assert.isDefined(scopeCall, 'scope error should have been called'); const errMsg = scopeCall[1]; assert.include(errMsg, 'worldInputs'); diff --git a/test/unit/webgl/p5.Texture.js b/test/unit/webgl/p5.Texture.js index ea566ddc82..7c3d79bc36 100644 --- a/test/unit/webgl/p5.Texture.js +++ b/test/unit/webgl/p5.Texture.js @@ -1,6 +1,6 @@ import p5 from '../../../src/app.js'; -suite('p5.Texture', function() { +suite('p5.Texture', function () { var myp5; var texImg1; var texImg2; @@ -10,12 +10,12 @@ suite('p5.Texture', function() { var canvas; let prevPixelRatio; - beforeEach(function() { + beforeEach(function () { prevPixelRatio = window.devicePixelRatio; window.devicePixelRatio = 1; return new Promise(done => { - myp5 = new p5(function(p) { - p.setup = async function() { + myp5 = new p5(function (p) { + p.setup = async function () { canvas = p.createCanvas(100, 100, p.WEBGL); p.pixelDensity(1); texImg1 = p.createGraphics(2, 2, p.WEBGL); @@ -28,28 +28,38 @@ suite('p5.Texture', function() { p.texture(imgElementPowerOfTwo); resolve(); }); - }).then(() => new Promise(resolve => { - p.createImg(texImg3.canvas.toDataURL(), '', 'anonymous', el => { - el.size(50, 50); - imgElementNotPowerOfTwo = el; - p.texture(imgElementNotPowerOfTwo); - resolve(); + }) + .then( + () => + new Promise(resolve => { + p.createImg( + texImg3.canvas.toDataURL(), + '', + 'anonymous', + el => { + el.size(50, 50); + imgElementNotPowerOfTwo = el; + p.texture(imgElementNotPowerOfTwo); + resolve(); + } + ); + }) + ) + .then(() => { + p.texture(texImg1); + done(); }); - })).then(() => { - p.texture(texImg1); - done(); - }); }; }); }); }); - afterEach(function() { + afterEach(function () { window.devicePixelRatio = prevPixelRatio; myp5.remove(); }); - var testTextureSet = function(src) { + var testTextureSet = function (src) { var lightShader = myp5._renderer._getLightShader(); var selectedShader = myp5._renderer._getFillShader(); console.log('first'); @@ -66,115 +76,221 @@ suite('p5.Texture', function() { assert(tex.src === src, 'texture did not have expected image as source'); }; - suite('p5.Texture', function() { + suite('p5.Texture', function () { let texParamSpy; beforeEach(() => { - texParamSpy = vi.spyOn(myp5._renderer.GL, 'texParameteri'); + texParamSpy = vi.spyOn(myp5._renderer.GL, 'texParameteri'); }); afterEach(() => { vi.restoreAllMocks(); }); - test('Create and cache a single texture with p5.Image', function() { + test('Create and cache a single texture with p5.Image', function () { testTextureSet(texImg1); }); - test('Create and cache multiple p5.Image textures', function() { + test('Create and cache multiple p5.Image textures', function () { myp5.texture(texImg2); testTextureSet(texImg2); // other image was made into a texture in setup, should still be there! //testTextureSet(texImg1); }); - test('Set filter mode to linear', function() { + test('Set filter mode to linear', function () { var tex = myp5._renderer.getTexture(texImg2); tex.setInterpolation(myp5.LINEAR, myp5.LINEAR); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_MIN_FILTER, myp5._renderer.GL.LINEAR); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_MAG_FILTER, myp5._renderer.GL.LINEAR); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_MIN_FILTER, + myp5._renderer.GL.LINEAR + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_MAG_FILTER, + myp5._renderer.GL.LINEAR + ); }); - test('Set filter mode to nearest', function() { + test('Set filter mode to nearest', function () { var tex = myp5._renderer.getTexture(texImg2); tex.setInterpolation(myp5.NEAREST, myp5.NEAREST); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_MIN_FILTER, myp5._renderer.GL.NEAREST); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_MAG_FILTER, myp5._renderer.GL.NEAREST); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_MIN_FILTER, + myp5._renderer.GL.NEAREST + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_MAG_FILTER, + myp5._renderer.GL.NEAREST + ); }); - test('Set wrap mode to clamp', function() { + test('Set wrap mode to clamp', function () { var tex = myp5._renderer.getTexture(texImg2); tex.setWrapMode(myp5.CLAMP, myp5.CLAMP); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.CLAMP_TO_EDGE); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.CLAMP_TO_EDGE); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.CLAMP_TO_EDGE + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.CLAMP_TO_EDGE + ); }); - test('Set wrap mode to repeat', function() { + test('Set wrap mode to repeat', function () { var tex = myp5._renderer.getTexture(texImg2); tex.setWrapMode(myp5.REPEAT, myp5.REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.REPEAT); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.REPEAT + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.REPEAT + ); }); - test('Set wrap mode to mirror', function() { + test('Set wrap mode to mirror', function () { var tex = myp5._renderer.getTexture(texImg2); tex.setWrapMode(myp5.MIRROR, myp5.MIRROR); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.MIRRORED_REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.MIRRORED_REPEAT); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.MIRRORED_REPEAT + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.MIRRORED_REPEAT + ); }); - test('Set wrap mode REPEAT if src dimensions is powerOfTwo', function() { + test('Set wrap mode REPEAT if src dimensions is powerOfTwo', function () { const tex = myp5._renderer.getTexture(imgElementPowerOfTwo); tex.setWrapMode(myp5.REPEAT, myp5.REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.REPEAT); - }); - test( - 'Set default wrap mode REPEAT if WEBGL2 and src dimensions != powerOfTwo', - function() { - const tex = myp5._renderer.getTexture(imgElementNotPowerOfTwo); - tex.setWrapMode(myp5.REPEAT, myp5.REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.REPEAT); - } - ); - test( - 'Set default wrap mode CLAMP if WEBGL1 and src dimensions != powerOfTwo', - function() { - myp5.setAttributes({ version: 1 }); - texParamSpy = vi.spyOn(myp5._renderer.GL, 'texParameteri'); - const tex = myp5._renderer.getTexture(imgElementNotPowerOfTwo); - tex.setWrapMode(myp5.REPEAT, myp5.REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.CLAMP_TO_EDGE); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.CLAMP_TO_EDGE); - } - ); - test('Set textureMode to NORMAL', function() { + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.REPEAT + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.REPEAT + ); + }); + test('Set default wrap mode REPEAT if WEBGL2 and src dimensions != powerOfTwo', function () { + const tex = myp5._renderer.getTexture(imgElementNotPowerOfTwo); + tex.setWrapMode(myp5.REPEAT, myp5.REPEAT); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.REPEAT + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.REPEAT + ); + }); + test('Set default wrap mode CLAMP if WEBGL1 and src dimensions != powerOfTwo', function () { + myp5.setAttributes({ version: 1 }); + texParamSpy = vi.spyOn(myp5._renderer.GL, 'texParameteri'); + const tex = myp5._renderer.getTexture(imgElementNotPowerOfTwo); + tex.setWrapMode(myp5.REPEAT, myp5.REPEAT); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.CLAMP_TO_EDGE + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.CLAMP_TO_EDGE + ); + }); + test('Set textureMode to NORMAL', function () { myp5.textureMode(myp5.NORMAL); assert.deepEqual(myp5._renderer.states.textureMode, myp5.NORMAL); }); - test('Set textureMode to IMAGE', function() { + test('Set textureMode to IMAGE', function () { myp5.textureMode(myp5.IMAGE); assert.deepEqual(myp5._renderer.states.textureMode, myp5.IMAGE); }); - test('Set global wrap mode to clamp', function() { + test('Set global wrap mode to clamp', function () { myp5.textureWrap(myp5.CLAMP); var tex1 = myp5._renderer.getTexture(texImg1); var tex2 = myp5._renderer.getTexture(texImg2); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.CLAMP_TO_EDGE); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.CLAMP_TO_EDGE); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.CLAMP_TO_EDGE); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.CLAMP_TO_EDGE); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.CLAMP_TO_EDGE + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.CLAMP_TO_EDGE + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.CLAMP_TO_EDGE + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.CLAMP_TO_EDGE + ); }); - test('Set global wrap mode to repeat', function() { + test('Set global wrap mode to repeat', function () { myp5.textureWrap(myp5.REPEAT); var tex1 = myp5._renderer.getTexture(texImg1); var tex2 = myp5._renderer.getTexture(texImg2); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.REPEAT); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.REPEAT + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.REPEAT + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.REPEAT + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.REPEAT + ); }); - test('Set global wrap mode to mirror', function() { + test('Set global wrap mode to mirror', function () { myp5.textureWrap(myp5.MIRROR); var tex1 = myp5._renderer.getTexture(texImg1); var tex2 = myp5._renderer.getTexture(texImg2); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.MIRRORED_REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.MIRRORED_REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_S, myp5._renderer.GL.MIRRORED_REPEAT); - expect(texParamSpy).toHaveBeenCalledWith(myp5._renderer.GL.TEXTURE_2D, myp5._renderer.GL.TEXTURE_WRAP_T, myp5._renderer.GL.MIRRORED_REPEAT); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.MIRRORED_REPEAT + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.MIRRORED_REPEAT + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_S, + myp5._renderer.GL.MIRRORED_REPEAT + ); + expect(texParamSpy).toHaveBeenCalledWith( + myp5._renderer.GL.TEXTURE_2D, + myp5._renderer.GL.TEXTURE_WRAP_T, + myp5._renderer.GL.MIRRORED_REPEAT + ); }); - test('Handles changes to p5.Image size', function() { + test('Handles changes to p5.Image size', function () { const tex = myp5._renderer.getTexture(texImg2); expect(tex.width).to.equal(texImg2.width); expect(tex.height).to.equal(texImg2.height); @@ -183,7 +299,7 @@ suite('p5.Texture', function() { expect(tex.width).to.equal(texImg2.width); expect(tex.height).to.equal(texImg2.height); }); - test('Handles changes to p5.Graphics size', function() { + test('Handles changes to p5.Graphics size', function () { const tex = myp5._renderer.getTexture(texImg1); expect(tex.width).to.equal(texImg1.width); expect(tex.height).to.equal(texImg1.height); @@ -192,7 +308,7 @@ suite('p5.Texture', function() { expect(tex.width).to.equal(texImg1.width); expect(tex.height).to.equal(texImg1.height); }); - test('Handles changes to p5.Renderer size', function() { + test('Handles changes to p5.Renderer size', function () { const tex = texImg1._renderer.getTexture(canvas); expect(tex.width).to.equal(canvas.width); expect(tex.height).to.equal(canvas.height); diff --git a/test/unit/webgpu/p5.Framebuffer.js b/test/unit/webgpu/p5.Framebuffer.js index ccbadbc7a0..e395849051 100644 --- a/test/unit/webgpu/p5.Framebuffer.js +++ b/test/unit/webgpu/p5.Framebuffer.js @@ -1,31 +1,31 @@ import p5 from '../../../src/app.js'; -import rendererWebGPU from "../../../src/webgpu/p5.RendererWebGPU"; +import rendererWebGPU from '../../../src/webgpu/p5.RendererWebGPU'; p5.registerAddon(rendererWebGPU); -suite('WebGPU p5.Framebuffer', function() { +suite('WebGPU p5.Framebuffer', function () { let myp5; let prevPixelRatio; - beforeAll(async function() { + beforeAll(async function () { prevPixelRatio = window.devicePixelRatio; window.devicePixelRatio = 1; - myp5 = new p5(function(p) { - p.setup = function() {}; + myp5 = new p5(function (p) { + p.setup = function () {}; }); }); - beforeEach(async function() { + beforeEach(async function () { await myp5.createCanvas(10, 10, 'webgpu'); - }) + }); - afterAll(function() { + afterAll(function () { myp5.remove(); window.devicePixelRatio = prevPixelRatio; }); - suite('Creation and basic properties', function() { - test('framebuffers can be created with WebGPU renderer', async function() { + suite('Creation and basic properties', function () { + test('framebuffers can be created with WebGPU renderer', async function () { const fbo = myp5.createFramebuffer(); expect(fbo).to.be.an('object'); @@ -34,7 +34,7 @@ suite('WebGPU p5.Framebuffer', function() { expect(fbo.autoSized()).to.equal(true); }); - test('framebuffers can be created with custom dimensions', async function() { + test('framebuffers can be created with custom dimensions', async function () { const fbo = myp5.createFramebuffer({ width: 20, height: 30 }); expect(fbo.width).to.equal(20); @@ -42,14 +42,14 @@ suite('WebGPU p5.Framebuffer', function() { expect(fbo.autoSized()).to.equal(false); }); - test('framebuffers have color texture', async function() { + test('framebuffers have color texture', async function () { const fbo = myp5.createFramebuffer(); expect(fbo.color).to.be.an('object'); expect(fbo.color.rawTexture).to.be.a('function'); }); - test('framebuffers can specify different formats', async function() { + test('framebuffers can specify different formats', async function () { const fbo = myp5.createFramebuffer({ format: 'float', channels: 'rgb' @@ -61,8 +61,8 @@ suite('WebGPU p5.Framebuffer', function() { }); }); - suite('Auto-sizing behavior', function() { - test('auto-sized framebuffers change size with canvas', async function() { + suite('Auto-sizing behavior', function () { + test('auto-sized framebuffers change size with canvas', async function () { myp5.pixelDensity(1); const fbo = myp5.createFramebuffer(); @@ -78,7 +78,7 @@ suite('WebGPU p5.Framebuffer', function() { expect(fbo.density).to.equal(2); }); - test('manually-sized framebuffers do not change size with canvas', async function() { + test('manually-sized framebuffers do not change size with canvas', async function () { myp5.pixelDensity(3); const fbo = myp5.createFramebuffer({ width: 25, height: 30, density: 1 }); @@ -94,7 +94,7 @@ suite('WebGPU p5.Framebuffer', function() { expect(fbo.density).to.equal(1); }); - test('manually-sized framebuffers can be made auto-sized', async function() { + test('manually-sized framebuffers can be made auto-sized', async function () { myp5.pixelDensity(1); const fbo = myp5.createFramebuffer({ width: 25, height: 30, density: 2 }); @@ -115,8 +115,8 @@ suite('WebGPU p5.Framebuffer', function() { }); }); - suite('Manual resizing', function() { - test('framebuffers can be manually resized', async function() { + suite('Manual resizing', function () { + test('framebuffers can be manually resized', async function () { myp5.pixelDensity(1); const fbo = myp5.createFramebuffer(); @@ -130,7 +130,7 @@ suite('WebGPU p5.Framebuffer', function() { expect(fbo.autoSized()).to.equal(false); }); - test('resizing affects pixel density', async function() { + test('resizing affects pixel density', async function () { myp5.pixelDensity(1); const fbo = myp5.createFramebuffer(); @@ -145,8 +145,8 @@ suite('WebGPU p5.Framebuffer', function() { }); }); - suite('Drawing functionality', function() { - test('can draw to framebuffer with draw() method', async function() { + suite('Drawing functionality', function () { + test('can draw to framebuffer with draw() method', async function () { const fbo = myp5.createFramebuffer(); myp5.background(0, 255, 0); @@ -171,7 +171,7 @@ suite('WebGPU p5.Framebuffer', function() { expect([...myp5.pixels.slice(0, 3)]).toEqual([0, 0, 255]); }); - test('can use framebuffer as texture', async function() { + test('can use framebuffer as texture', async function () { const fbo = myp5.createFramebuffer(); fbo.draw(() => { @@ -186,8 +186,8 @@ suite('WebGPU p5.Framebuffer', function() { }); }); - suite('Pixel access', function() { - test('loadPixels returns a promise in WebGPU', async function() { + suite('Pixel access', function () { + test('loadPixels returns a promise in WebGPU', async function () { const fbo = myp5.createFramebuffer(); fbo.draw(() => { @@ -203,7 +203,7 @@ suite('WebGPU p5.Framebuffer', function() { expect([...pixels.slice(0, 4)]).toEqual([255, 0, 0, 255]); }); - test('pixels property is set after loadPixels resolves', async function() { + test('pixels property is set after loadPixels resolves', async function () { const fbo = myp5.createFramebuffer(); fbo.draw(() => { @@ -215,7 +215,7 @@ suite('WebGPU p5.Framebuffer', function() { expect(fbo.pixels.length).to.equal(10 * 10 * 4); }); - test('get() returns a promise for single pixel in WebGPU', async function() { + test('get() returns a promise for single pixel in WebGPU', async function () { const fbo = myp5.createFramebuffer(); fbo.draw(() => { @@ -231,7 +231,7 @@ suite('WebGPU p5.Framebuffer', function() { expect([...color]).toEqual([100, 150, 200, 255]); }); - test('get() returns a promise for region in WebGPU', async function() { + test('get() returns a promise for region in WebGPU', async function () { const fbo = myp5.createFramebuffer(); fbo.draw(() => { diff --git a/test/unit/webgpu/p5.RendererWebGPU.js b/test/unit/webgpu/p5.RendererWebGPU.js index 3a21f174f6..87b8ef6b5d 100644 --- a/test/unit/webgpu/p5.RendererWebGPU.js +++ b/test/unit/webgpu/p5.RendererWebGPU.js @@ -1,34 +1,34 @@ import p5 from '../../../src/app.js'; -import rendererWebGPU from "../../../src/webgpu/p5.RendererWebGPU"; +import rendererWebGPU from '../../../src/webgpu/p5.RendererWebGPU'; p5.registerAddon(rendererWebGPU); -suite('WebGPU p5.RendererWebGPU', function() { +suite('WebGPU p5.RendererWebGPU', function () { let myp5; let prevPixelRatio; - beforeAll(async function() { + beforeAll(async function () { prevPixelRatio = window.devicePixelRatio; window.devicePixelRatio = 1; - myp5 = new p5(function(p) { - p.setup = function() {}; + myp5 = new p5(function (p) { + p.setup = function () {}; }); }); - beforeEach(async function() { + beforeEach(async function () { await myp5.createCanvas(50, 50, myp5.WEBGPU); }); - afterEach(function() { + afterEach(function () { myp5.remove(); }); - afterAll(function() { + afterAll(function () { window.devicePixelRatio = prevPixelRatio; }); - suite('Buffer Pooling', function() { - test('drawing geometry twice reuses vertex buffers', async function() { + suite('Buffer Pooling', function () { + test('drawing geometry twice reuses vertex buffers', async function () { // Create a simple geometry const geom = myp5.buildGeometry(() => { myp5.triangle(0, 0, 10, 0, 5, 10); @@ -42,7 +42,8 @@ suite('WebGPU p5.RendererWebGPU', function() { const poolForVertexBuffer = geom._vertexBufferPools?.vertexBuffer; expect(poolForVertexBuffer).to.exist; const initialPoolSize = poolForVertexBuffer.length; - const initialInUseSize = geom._vertexBuffersInUse?.vertexBuffer?.length || 0; + const initialInUseSize = + geom._vertexBuffersInUse?.vertexBuffer?.length || 0; // Draw the geometry again myp5.background(255); @@ -50,14 +51,17 @@ suite('WebGPU p5.RendererWebGPU', function() { // Verify the pool hasn't grown - buffers should be reused const finalPoolSize = poolForVertexBuffer.length; - const finalInUseSize = geom._vertexBuffersInUse?.vertexBuffer?.length || 0; + const finalInUseSize = + geom._vertexBuffersInUse?.vertexBuffer?.length || 0; // Pool size should stay the same or be smaller (buffers moved from pool to in-use) // The total number of buffers (pool + in-use) should remain constant - expect(initialPoolSize + initialInUseSize).to.equal(finalPoolSize + finalInUseSize); + expect(initialPoolSize + initialInUseSize).to.equal( + finalPoolSize + finalInUseSize + ); }); - test('freeGeometry causes new buffer allocation on next draw', async function() { + test('freeGeometry causes new buffer allocation on next draw', async function () { // Create a simple geometry const geom = myp5.buildGeometry(() => { myp5.triangle(0, 0, 10, 0, 5, 10); @@ -70,7 +74,8 @@ suite('WebGPU p5.RendererWebGPU', function() { // Get initial buffer count const poolForVertexBuffer = geom._vertexBufferPools?.vertexBuffer; expect(poolForVertexBuffer).to.exist; - const initialTotalBuffers = poolForVertexBuffer.length + + const initialTotalBuffers = + poolForVertexBuffer.length + (geom._vertexBuffersInUse?.vertexBuffer?.length || 0); // Free the geometry @@ -81,7 +86,8 @@ suite('WebGPU p5.RendererWebGPU', function() { myp5.model(geom); // After freeGeometry, new buffers should be allocated - const finalTotalBuffers = poolForVertexBuffer.length + + const finalTotalBuffers = + poolForVertexBuffer.length + (geom._vertexBuffersInUse?.vertexBuffer?.length || 0); // We should have more buffers now since freeGeometry marks geometry as dirty @@ -89,7 +95,7 @@ suite('WebGPU p5.RendererWebGPU', function() { expect(finalTotalBuffers).to.be.greaterThan(initialTotalBuffers); }); - test('immediate mode geometry reuses buffers across frames', async function() { + test('immediate mode geometry reuses buffers across frames', async function () { // Function to draw the same shape using immediate mode const drawSameShape = () => { myp5.background(255); @@ -106,10 +112,12 @@ suite('WebGPU p5.RendererWebGPU', function() { // Get the immediate mode geometry (shapeBuilder geometry) const immediateGeom = myp5._renderer.shapeBuilder.geometry; - const poolForVertexBuffer = immediateGeom._vertexBufferPools?.vertexBuffer; + const poolForVertexBuffer = + immediateGeom._vertexBufferPools?.vertexBuffer; expect(poolForVertexBuffer).to.exist; - const initialTotalBuffers = poolForVertexBuffer.length + + const initialTotalBuffers = + poolForVertexBuffer.length + (immediateGeom._vertexBuffersInUse?.vertexBuffer?.length || 0); // Draw the same shape for several more frames @@ -118,31 +126,34 @@ suite('WebGPU p5.RendererWebGPU', function() { await myp5._renderer.finishDraw(); // Check that total buffer count hasn't increased - const currentTotalBuffers = poolForVertexBuffer.length + + const currentTotalBuffers = + poolForVertexBuffer.length + (immediateGeom._vertexBuffersInUse?.vertexBuffer?.length || 0); - expect(currentTotalBuffers).to.equal(initialTotalBuffers, - `Buffer count should stay constant across frames (frame ${frame})`); + expect(currentTotalBuffers).to.equal( + initialTotalBuffers, + `Buffer count should stay constant across frames (frame ${frame})` + ); } }); }); - suite('noSmooth()', function() { - test('disables antialiasing on the main canvas framebuffer', async function() { + suite('noSmooth()', function () { + test('disables antialiasing on the main canvas framebuffer', async function () { await myp5.noSmooth(); expect(myp5._renderer.mainFramebuffer.antialias).to.equal(false); }); }); - suite('Stability', function() { - test('pixelDensity() after setAttributes() should not crash', async function() { + suite('Stability', function () { + test('pixelDensity() after setAttributes() should not crash', async function () { // This test simulates the issue where a synchronous call (pixelDensity) // happens before an asynchronous initialization (setAttributes -> _resetContext) // is complete. await new Promise((resolve, reject) => { try { myp5 = new p5(p => { - p.setup = async function() { + p.setup = async function () { try { await p.createCanvas(100, 100, p.WEBGPU); @@ -168,8 +179,8 @@ suite('WebGPU p5.RendererWebGPU', function() { }); }); - suite('StorageBuffer.read()', function() { - test('reads back float array data', async function() { + suite('StorageBuffer.read()', function () { + test('reads back float array data', async function () { const input = new Float32Array([1, 2, 3, 4]); const buf = myp5.createStorage(input); @@ -182,10 +193,10 @@ suite('WebGPU p5.RendererWebGPU', function() { } }); - test('reads back struct array data', async function() { + test('reads back struct array data', async function () { const input = [ { x: 1.0, y: 2.0 }, - { x: 3.0, y: 4.0 }, + { x: 3.0, y: 4.0 } ]; const buf = myp5.createStorage(input); @@ -199,7 +210,7 @@ suite('WebGPU p5.RendererWebGPU', function() { } }); - test('read after update returns new data', async function() { + test('read after update returns new data', async function () { const buf = myp5.createStorage(new Float32Array([10, 20, 30])); const updated = new Float32Array([100, 200, 300]); buf.update(updated); @@ -211,10 +222,10 @@ suite('WebGPU p5.RendererWebGPU', function() { } }); - test('reads back struct with vector fields as p5.Vector', async function() { + test('reads back struct with vector fields as p5.Vector', async function () { const input = [ { position: myp5.createVector(1, 2), speed: 5.0 }, - { position: myp5.createVector(3, 4), speed: 10.0 }, + { position: myp5.createVector(3, 4), speed: 10.0 } ]; const buf = myp5.createStorage(input); @@ -233,15 +244,18 @@ suite('WebGPU p5.RendererWebGPU', function() { expect(result[1].speed).to.be.closeTo(10.0, 0.001); }); - test('reads back data modified by a compute shader', async function() { + test('reads back data modified by a compute shader', async function () { const input = new Float32Array([1, 2, 3, 4]); const buf = myp5.createStorage(input); - const computeShader = myp5.buildComputeShader(() => { - const d = myp5.uniformStorage(); - const idx = myp5.index.x; - d[idx] = d[idx] * 2; - }, { myp5 }); + const computeShader = myp5.buildComputeShader( + () => { + const d = myp5.uniformStorage(); + const idx = myp5.index.x; + d[idx] = d[idx] * 2; + }, + { myp5 } + ); computeShader.setUniform('d', buf); myp5.compute(computeShader, 4); @@ -255,8 +269,8 @@ suite('WebGPU p5.RendererWebGPU', function() { }); }); - suite('StorageBuffer.set()', function() { - test('updates a single float value at the given index', async function() { + suite('StorageBuffer.set()', function () { + test('updates a single float value at the given index', async function () { const buf = myp5.createStorage(new Float32Array([1, 2, 3, 4])); buf.set(2, 9.5); @@ -268,11 +282,11 @@ suite('WebGPU p5.RendererWebGPU', function() { expect(result[3]).to.be.closeTo(4, 0.001); }); - test('updates a single struct element without touching neighbours', async function() { + test('updates a single struct element without touching neighbours', async function () { const input = [ { x: 1.0, y: 2.0 }, { x: 3.0, y: 4.0 }, - { x: 5.0, y: 6.0 }, + { x: 5.0, y: 6.0 } ]; const buf = myp5.createStorage(input); @@ -293,7 +307,7 @@ suite('WebGPU p5.RendererWebGPU', function() { expect(result[2].y).to.be.closeTo(6.0, 0.001); }); - test('set() then read() reflects the new value immediately', async function() { + test('set() then read() reflects the new value immediately', async function () { const buf = myp5.createStorage(new Float32Array([0, 0, 0])); buf.set(0, 42); @@ -301,39 +315,42 @@ suite('WebGPU p5.RendererWebGPU', function() { expect(result[0]).to.be.closeTo(42, 0.001); }); - test('throws on out-of-bounds index for float buffer', function() { + test('throws on out-of-bounds index for float buffer', function () { const buf = myp5.createStorage(new Float32Array([1, 2, 3])); expect(() => buf.set(10, 5.0)).to.throw(); }); - test('throws on out-of-bounds index for struct buffer', function() { + test('throws on out-of-bounds index for struct buffer', function () { const buf = myp5.createStorage([{ x: 1.0 }, { x: 2.0 }]); expect(() => buf.set(99, { x: 3.0 })).to.throw(); }); - test('throws when passing a non-number to a float buffer', function() { + test('throws when passing a non-number to a float buffer', function () { const buf = myp5.createStorage(new Float32Array([1, 2, 3])); expect(() => buf.set(0, { x: 1 })).to.throw(); }); - test('throws when passing a non-object to a struct buffer', function() { + test('throws when passing a non-object to a struct buffer', function () { const buf = myp5.createStorage([{ x: 1.0 }, { x: 2.0 }]); expect(() => buf.set(0, 42)).to.throw(); }); }); - suite('p5.strands', function() { - test('a uniform whose name matches a hook parameter name does not break', async function() { + suite('p5.strands', function () { + test('a uniform whose name matches a hook parameter name does not break', async function () { myp5.pixelDensity(1); // 'color' is the WGSL parameter name of the getFinalColor hook's first argument. // Creating a uniform with the same name used to cause a WGSL name clash. - const myShader = myp5.baseColorShader().modify(() => { - const color = myp5.uniformFloat('color', 0.5); - myp5.finalColor.begin(); - myp5.finalColor.set([color, color, color, 1]); - myp5.finalColor.end(); - }, { myp5 }); + const myShader = myp5.baseColorShader().modify( + () => { + const color = myp5.uniformFloat('color', 0.5); + myp5.finalColor.begin(); + myp5.finalColor.set([color, color, color, 1]); + myp5.finalColor.end(); + }, + { myp5 } + ); myp5.background(0); myp5.noStroke(); diff --git a/test/unit/webgpu/p5.Shader.js b/test/unit/webgpu/p5.Shader.js index 249fba6f87..3c99025706 100644 --- a/test/unit/webgpu/p5.Shader.js +++ b/test/unit/webgpu/p5.Shader.js @@ -1,15 +1,15 @@ import p5 from '../../../src/app.js'; -import rendererWebGPU from "../../../src/webgpu/p5.RendererWebGPU"; +import rendererWebGPU from '../../../src/webgpu/p5.RendererWebGPU'; p5.registerAddon(rendererWebGPU); -suite('WebGPU p5.Shader', function() { +suite('WebGPU p5.Shader', function () { var myp5; - beforeAll(function() { + beforeAll(function () { window.IS_MINIFIED = true; - myp5 = new p5(function(p) { - p.setup = function() { + myp5 = new p5(function (p) { + p.setup = function () { p.createCanvas(100, 100, 'webgpu'); p.pointLight(250, 250, 250, 100, 100, 0); p.ambientMaterial(250); @@ -17,24 +17,23 @@ suite('WebGPU p5.Shader', function() { }); }); - afterAll(function() { + afterAll(function () { myp5.remove(); }); suite('p5.strands', () => { test('does not break when arrays are in uniform callbacks', async () => { await myp5.createCanvas(5, 5, myp5.WEBGPU); - const myShader = myp5.baseMaterialShader().modify(() => { - const size = myp5.uniformVector2(() => [myp5.width, myp5.height]); - myp5.getPixelInputs(inputs => { - inputs.color = [ - size / 1000, - 0, - 1 - ]; - return inputs; - }); - }, { myp5 }); + const myShader = myp5.baseMaterialShader().modify( + () => { + const size = myp5.uniformVector2(() => [myp5.width, myp5.height]); + myp5.getPixelInputs(inputs => { + inputs.color = [size / 1000, 0, 1]; + return inputs; + }); + }, + { myp5 } + ); expect(() => { myp5.shader(myShader); myp5.plane(myp5.width, myp5.height); @@ -44,17 +43,20 @@ suite('WebGPU p5.Shader', function() { suite('if statement conditionals', () => { test('handle simple if statement with true condition', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.5); // initial gray - if (condition > 0.5) { - color = myp5.float(1.0); // set to white in if branch - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.5); // initial gray + if (condition > 0.5) { + color = myp5.float(1.0); // set to white in if branch + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -67,17 +69,20 @@ suite('WebGPU p5.Shader', function() { test('handle simple if statement with simpler assignment', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let color = 1; // initial gray - if (condition > 0.5) { - color = 1; // set to white in if branch - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let color = 1; // initial gray + if (condition > 0.5) { + color = 1; // set to white in if branch + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -90,17 +95,20 @@ suite('WebGPU p5.Shader', function() { test('handle simple if statement with false condition', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 0.0); // false condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.5); // initial gray - if (condition > 0.5) { - color = myp5.float(1.0); // set to white in if branch - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 0.0); // false condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.5); // initial gray + if (condition > 0.5) { + color = myp5.float(1.0); // set to white in if branch + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -113,19 +121,22 @@ suite('WebGPU p5.Shader', function() { test('handle if-else statement', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 0.0); // false condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.5); // initial gray - if (condition > 0.5) { - color = myp5.float(1.0); // white for true - } else { - color = myp5.float(0.0); // black for false - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 0.0); // false condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.5); // initial gray + if (condition > 0.5) { + color = myp5.float(1.0); // white for true + } else { + color = myp5.float(0.0); // black for false + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -138,21 +149,24 @@ suite('WebGPU p5.Shader', function() { test('handle multiple variable assignments in if statement', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let red = myp5.float(0.0); - let green = myp5.float(0.0); - let blue = myp5.float(0.0); - if (condition > 0.5) { - red = myp5.float(1.0); - green = myp5.float(0.5); - blue = myp5.float(0.0); - } - inputs.color = [red, green, blue, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let red = myp5.float(0.0); + let green = myp5.float(0.0); + let blue = myp5.float(0.0); + if (condition > 0.5) { + red = myp5.float(1.0); + green = myp5.float(0.5); + blue = myp5.float(0.0); + } + inputs.color = [red, green, blue, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -160,26 +174,29 @@ suite('WebGPU p5.Shader', function() { const pixelColor = await myp5.get(25, 25); assert.approximately(pixelColor[0], 255, 5); // Red channel should be 255 assert.approximately(pixelColor[1], 127, 5); // Green channel should be ~127 - assert.approximately(pixelColor[2], 0, 5); // Blue channel should be ~0 + assert.approximately(pixelColor[2], 0, 5); // Blue channel should be ~0 }); test('handle modifications after if statement', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const condition = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); // start with black - if (condition > 0.5) { - color = myp5.float(1.0); // set to white in if branch - } else { - color = myp5.float(0.5); // set to gray in else branch - } - // Modify the color after the if statement - color = color * 0.5; // Should result in 0.5 * 1.0 = 0.5 (gray) - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const condition = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); // start with black + if (condition > 0.5) { + color = myp5.float(1.0); // set to white in if branch + } else { + color = myp5.float(0.5); // set to gray in else branch + } + // Modify the color after the if statement + color = color * 0.5; // Should result in 0.5 * 1.0 = 0.5 (gray) + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -192,30 +209,33 @@ suite('WebGPU p5.Shader', function() { test('handle modifications after if statement in both branches', async () => { await myp5.createCanvas(100, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const uv = inputs.texCoord; - const condition = uv.x > 0.5; // left half false, right half true - let color = myp5.float(0.0); - if (condition) { - color = myp5.float(1.0); // white on right side - } else { - color = myp5.float(0.8); // light gray on left side - } - // Multiply by 0.5 after the if statement - color = color * 0.5; - // Right side: 1.0 * 0.5 = 0.5 (medium gray) - // Left side: 0.8 * 0.5 = 0.4 (darker gray) - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const uv = inputs.texCoord; + const condition = uv.x > 0.5; // left half false, right half true + let color = myp5.float(0.0); + if (condition) { + color = myp5.float(1.0); // white on right side + } else { + color = myp5.float(0.8); // light gray on left side + } + // Multiply by 0.5 after the if statement + color = color * 0.5; + // Right side: 1.0 * 0.5 = 0.5 (medium gray) + // Left side: 0.8 * 0.5 = 0.4 (darker gray) + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); // Check left side (false condition) - const leftPixel = await myp5.get(25, 25) - const rightPixel = await myp5.get(75, 25) + const leftPixel = await myp5.get(25, 25); + const rightPixel = await myp5.get(75, 25); assert.approximately(leftPixel[0], 102, 5); // 0.4 * 255 ≈ 102 // Check right side (true condition) assert.approximately(rightPixel[0], 127, 5); // 0.5 * 255 ≈ 127 @@ -223,21 +243,24 @@ suite('WebGPU p5.Shader', function() { test('handle if-else-if chains', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const value = myp5.uniformFloat(() => 0.5); // middle value - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); - if (value > 0.8) { - color = myp5.float(1.0); // white for high values - } else if (value > 0.3) { - color = myp5.float(0.5); // gray for medium values - } else { - color = myp5.float(0.0); // black for low values - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const value = myp5.uniformFloat(() => 0.5); // middle value + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); + if (value > 0.8) { + color = myp5.float(1.0); // white for high values + } else if (value > 0.3) { + color = myp5.float(0.5); // gray for medium values + } else { + color = myp5.float(0.0); // black for low values + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -250,21 +273,24 @@ suite('WebGPU p5.Shader', function() { test('handle if-else-if chains in the else branch', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const value = myp5.uniformFloat(() => 0.2); // middle value - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); - if (value > 0.8) { - color = myp5.float(1.0); // white for high values - } else if (value > 0.3) { - color = myp5.float(0.5); // gray for medium values - } else { - color = myp5.float(0.0); // black for low values - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + const value = myp5.uniformFloat(() => 0.2); // middle value + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); + if (value > 0.8) { + color = myp5.float(1.0); // white for high values + } else if (value > 0.3) { + color = myp5.float(0.5); // gray for medium values + } else { + color = myp5.float(0.0); // black for low values + } + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -277,29 +303,32 @@ suite('WebGPU p5.Shader', function() { test('handle conditional assignment in if-else-if chains', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const val = myp5.uniformFloat(() => Math.PI * 8); - myp5.getPixelInputs(inputs => { - let shininess = 0 - let color = 0 - if (val > 5) { - const elevation = myp5.sin(val) - if (elevation > 0.4) { - shininess = 0; - } else if (elevation > 0.25) { - shininess = 30; + const testShader = myp5.baseMaterialShader().modify( + () => { + const val = myp5.uniformFloat(() => Math.PI * 8); + myp5.getPixelInputs(inputs => { + let shininess = 0; + let color = 0; + if (val > 5) { + const elevation = myp5.sin(val); + if (elevation > 0.4) { + shininess = 0; + } else if (elevation > 0.25) { + shininess = 30; + } else { + color = 1; + shininess = 100; + } } else { - color = 1; - shininess = 100; + shininess += 25; } - } else { - shininess += 25; - } - inputs.shininess = shininess; - inputs.color = [color, color, color, 1]; - return inputs; - }); - }, { myp5 }); + inputs.shininess = shininess; + inputs.color = [color, color, color, 1]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -312,24 +341,27 @@ suite('WebGPU p5.Shader', function() { test('handle nested if statements', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const outerCondition = myp5.uniformFloat(() => 1.0); // true - const innerCondition = myp5.uniformFloat(() => 1.0); // true - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); - if (outerCondition > 0.5) { - if (innerCondition > 0.5) { - color = myp5.float(1.0); // white for both conditions true + const testShader = myp5.baseMaterialShader().modify( + () => { + const outerCondition = myp5.uniformFloat(() => 1.0); // true + const innerCondition = myp5.uniformFloat(() => 1.0); // true + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); + if (outerCondition > 0.5) { + if (innerCondition > 0.5) { + color = myp5.float(1.0); // white for both conditions true + } else { + color = myp5.float(0.5); // gray for outer true, inner false + } } else { - color = myp5.float(0.5); // gray for outer true, inner false + color = myp5.float(0.0); // black for outer false } - } else { - color = myp5.float(0.0); // black for outer false - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -343,25 +375,27 @@ suite('WebGPU p5.Shader', function() { // Keep one direct API test for completeness test('handle direct StrandsIf API usage', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const conditionValue = myp5.uniformFloat(() => 1.0); // true condition - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.5); // initial gray - const assignments = myp5.strandsIf( - conditionValue.greaterThan(0), - () => { - let tmp = color.copy(); - tmp = myp5.float(1.0); // set to white in if branch - return { color: tmp }; - } - ).Else(() => { - return { color: color }; // keep original in else branch + const testShader = myp5.baseMaterialShader().modify( + () => { + const conditionValue = myp5.uniformFloat(() => 1.0); // true condition + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.5); // initial gray + const assignments = myp5 + .strandsIf(conditionValue.greaterThan(0), () => { + let tmp = color.copy(); + tmp = myp5.float(1.0); // set to white in if branch + return { color: tmp }; + }) + .Else(() => { + return { color: color }; // keep original in else branch + }); + color = assignments.color; + inputs.color = [color, color, color, 1.0]; + return inputs; }); - color = assignments.color; - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -374,34 +408,34 @@ suite('WebGPU p5.Shader', function() { test('handle direct StrandsIf ElseIf API usage', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const value = myp5.uniformFloat(() => 0.5); // middle value - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); // initial black - const assignments = myp5.strandsIf( - value.greaterThan(0.8), - () => { - let tmp = color.copy(); - tmp = myp5.float(1.0); // white for high values - return { color: tmp }; - } - ).ElseIf( - value.greaterThan(0.3), - () => { - let tmp = color.copy(); - tmp = myp5.float(0.5); // gray for medium values - return { color: tmp }; - } - ).Else(() => { - let tmp = color.copy(); - tmp = myp5.float(0.0); // black for low values - return { color: tmp }; + const testShader = myp5.baseMaterialShader().modify( + () => { + const value = myp5.uniformFloat(() => 0.5); // middle value + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); // initial black + const assignments = myp5 + .strandsIf(value.greaterThan(0.8), () => { + let tmp = color.copy(); + tmp = myp5.float(1.0); // white for high values + return { color: tmp }; + }) + .ElseIf(value.greaterThan(0.3), () => { + let tmp = color.copy(); + tmp = myp5.float(0.5); // gray for medium values + return { color: tmp }; + }) + .Else(() => { + let tmp = color.copy(); + tmp = myp5.float(0.0); // black for low values + return { color: tmp }; + }); + color = assignments.color; + inputs.color = [color, color, color, 1.0]; + return inputs; }); - color = assignments.color; - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -415,22 +449,25 @@ suite('WebGPU p5.Shader', function() { test('using boolean intermediate variables', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor((inputs, canvasContent) => { - let value = 1; - let condition = 1 > 2; + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor((inputs, canvasContent) => { + let value = 1; + let condition = 1 > 2; - if (value < 0.5) { - condition = 0.5 < 2; - } + if (value < 0.5) { + condition = 0.5 < 2; + } - if (condition) { - return [1, 0, 0, 1] - } + if (condition) { + return [1, 0, 0, 1]; + } - return [0.4, 0, 0, 1]; - }); - }, { myp5 }); + return [0.4, 0, 0, 1]; + }); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -444,23 +481,26 @@ suite('WebGPU p5.Shader', function() { test('using boolean intermediate variables in functions', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseFilterShader().modify(() => { - const conditionMet = () => { - let condition = 1 > 2; - let value = 1; - if (value < 0.5) { - condition = 0.5 < 2; - } - return !condition - } - myp5.getColor((inputs, canvasContent) => { - if (conditionMet()) { - return [1, 0, 0, 1] - } + const testShader = myp5.baseFilterShader().modify( + () => { + const conditionMet = () => { + let condition = 1 > 2; + let value = 1; + if (value < 0.5) { + condition = 0.5 < 2; + } + return !condition; + }; + myp5.getColor((inputs, canvasContent) => { + if (conditionMet()) { + return [1, 0, 0, 1]; + } - return [0.4, 0, 0, 1]; - }); - }, { myp5 }); + return [0.4, 0, 0, 1]; + }); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -474,22 +514,25 @@ suite('WebGPU p5.Shader', function() { test('using boolean intermediate variables in functions with early returns', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseFilterShader().modify(() => { - const conditionMet = () => { - let value = 1; - if (value < 0.5) { - return true - } - return false - } - myp5.getColor((inputs, canvasContent) => { - if (conditionMet()) { - return [1, 0, 0, 1] - } + const testShader = myp5.baseFilterShader().modify( + () => { + const conditionMet = () => { + let value = 1; + if (value < 0.5) { + return true; + } + return false; + }; + myp5.getColor((inputs, canvasContent) => { + if (conditionMet()) { + return [1, 0, 0, 1]; + } - return [0.4, 0, 0, 1]; - }); - }, { myp5 }); + return [0.4, 0, 0, 1]; + }); + }, + { myp5 } + ); myp5.background(255, 255, 255); myp5.filter(testShader); @@ -504,12 +547,16 @@ suite('WebGPU p5.Shader', function() { suite('ternary expressions', () => { test('ternary changes color based on left/right side of canvas', async () => { await myp5.createCanvas(50, 25, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - inputs.color = inputs.texCoord.x > 0.5 ? [1, 0, 0, 1] : [0, 0, 1, 1]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + inputs.color = + inputs.texCoord.x > 0.5 ? [1, 0, 0, 1] : [0, 0, 1, 1]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -527,13 +574,16 @@ suite('WebGPU p5.Shader', function() { test('ternary with scalar values', async () => { await myp5.createCanvas(50, 25, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - const brightness = inputs.texCoord.x > 0.5 ? 1.0 : 0.0; - inputs.color = [brightness, brightness, brightness, 1]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + const brightness = inputs.texCoord.x > 0.5 ? 1.0 : 0.0; + inputs.color = [brightness, brightness, brightness, 1]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); myp5.plane(myp5.width, myp5.height); @@ -554,18 +604,21 @@ suite('WebGPU p5.Shader', function() { test('handle simple for loop with known iteration count', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let color = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let color = myp5.float(0.0); - for (let i = 0; i < 3; i++) { - color = color + 0.1; - } + for (let i = 0; i < 3; i++) { + color = color + 0.1; + } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -581,18 +634,21 @@ suite('WebGPU p5.Shader', function() { test('handle swizzle assignments in loops', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let color = [0, 0, 0, 1]; + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let color = [0, 0, 0, 1]; - for (let i = 0; i < 3; i++) { - color.rgb += 0.1; - } + for (let i = 0; i < 3; i++) { + color.rgb += 0.1; + } - inputs.color = color; - return inputs; - }); - }, { myp5 }); + inputs.color = color; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -608,20 +664,23 @@ suite('WebGPU p5.Shader', function() { test('handle for loop with variable as loop bound', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - const maxIterations = myp5.uniformInt(() => 2); + const testShader = myp5.baseMaterialShader().modify( + () => { + const maxIterations = myp5.uniformInt(() => 2); - myp5.getPixelInputs(inputs => { - let result = myp5.float(0.0); + myp5.getPixelInputs(inputs => { + let result = myp5.float(0.0); - for (let i = 0; i < maxIterations; i++) { - result = result + 0.25; - } + for (let i = 0; i < maxIterations; i++) { + result = result + 0.25; + } - inputs.color = [result, result, result, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [result, result, result, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -637,20 +696,23 @@ suite('WebGPU p5.Shader', function() { test('handle for loop modifying multiple variables', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let red = myp5.float(0.0); - let green = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let red = myp5.float(0.0); + let green = myp5.float(0.0); - for (let i = 0; i < 4; i++) { - red = red + 0.125; // 4 * 0.125 = 0.5 - green = green + 0.25; // 4 * 0.25 = 1.0 - } + for (let i = 0; i < 4; i++) { + red = red + 0.125; // 4 * 0.125 = 0.5 + green = green + 0.25; // 4 * 0.25 = 1.0 + } - inputs.color = [red, green, 0.0, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [red, green, 0.0, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -659,26 +721,29 @@ suite('WebGPU p5.Shader', function() { const pixelColor = await myp5.get(25, 25); assert.approximately(pixelColor[0], 127, 5); // 0.5 * 255 ≈ 127 assert.approximately(pixelColor[1], 255, 5); // 1.0 * 255 = 255 - assert.approximately(pixelColor[2], 0, 5); // 0.0 * 255 = 0 + assert.approximately(pixelColor[2], 0, 5); // 0.0 * 255 = 0 }); test('handle for loop with conditional inside', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let sum = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let sum = myp5.float(0.0); - for (let i = 0; i < 5; i++) { - if (i % 2 === 0) { - sum = sum + 0.1; // Add on even iterations: 0, 2, 4 + for (let i = 0; i < 5; i++) { + if (i % 2 === 0) { + sum = sum + 0.1; // Add on even iterations: 0, 2, 4 + } } - } - inputs.color = [sum, sum, sum, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [sum, sum, sum, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -694,20 +759,23 @@ suite('WebGPU p5.Shader', function() { test('handle nested for loops', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let total = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let total = myp5.float(0.0); - for (let i = 0; i < 2; i++) { - for (let j = 0; j < 3; j++) { - total = total + 0.05; // 2 * 3 = 6 iterations + for (let i = 0; i < 2; i++) { + for (let j = 0; j < 3; j++) { + total = total + 0.05; // 2 * 3 = 6 iterations + } } - } - inputs.color = [total, total, total, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [total, total, total, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -723,35 +791,38 @@ suite('WebGPU p5.Shader', function() { test('handle complex nested for loops with multiple phi assignments', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let outerSum = myp5.float(0.0); - let globalCounter = myp5.float(0.0); - - // Outer for loop modifying multiple variables - for (let i = 0; i < 2; i++) { - let innerSum = myp5.float(0.0); - let localCounter = myp5.float(0.0); - - // Inner for loop also modifying multiple variables - for (let j = 0; j < 2; j++) { - innerSum = innerSum + 0.1; - localCounter = localCounter + 1.0; - globalCounter = globalCounter + 0.5; // This modifies outer scope + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let outerSum = myp5.float(0.0); + let globalCounter = myp5.float(0.0); + + // Outer for loop modifying multiple variables + for (let i = 0; i < 2; i++) { + let innerSum = myp5.float(0.0); + let localCounter = myp5.float(0.0); + + // Inner for loop also modifying multiple variables + for (let j = 0; j < 2; j++) { + innerSum = innerSum + 0.1; + localCounter = localCounter + 1.0; + globalCounter = globalCounter + 0.5; // This modifies outer scope + } + + // Complex state modification between loops involving all variables + innerSum = innerSum * localCounter; // 0.2 * 2.0 = 0.4 + outerSum = outerSum + innerSum; // Add to outer sum + globalCounter = globalCounter * 0.5; // Modify global again } - // Complex state modification between loops involving all variables - innerSum = innerSum * localCounter; // 0.2 * 2.0 = 0.4 - outerSum = outerSum + innerSum; // Add to outer sum - globalCounter = globalCounter * 0.5; // Modify global again - } - - // Final result should be: 2 iterations * 0.4 = 0.8 for outerSum - // globalCounter: ((0 + 2*0.5)*0.5 + 2*0.5)*0.5 = ((1)*0.5 + 1)*0.5 = 1.5*0.5 = 0.75 - inputs.color = [outerSum, globalCounter, 0.0, 1.0]; - return inputs; - }); - }, { myp5 }); + // Final result should be: 2 iterations * 0.4 = 0.8 for outerSum + // globalCounter: ((0 + 2*0.5)*0.5 + 2*0.5)*0.5 = ((1)*0.5 + 1)*0.5 = 1.5*0.5 = 0.75 + inputs.color = [outerSum, globalCounter, 0.0, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -766,28 +837,31 @@ suite('WebGPU p5.Shader', function() { test('handle nested for loops with state modification between loops', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let total = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let total = myp5.float(0.0); - // Outer for loop - for (let i = 0; i < 2; i++) { - let innerSum = myp5.float(0.0); + // Outer for loop + for (let i = 0; i < 2; i++) { + let innerSum = myp5.float(0.0); - // Inner for loop - for (let j = 0; j < 3; j++) { - innerSum = innerSum + 0.1; // 3 * 0.1 = 0.3 per outer iteration - } + // Inner for loop + for (let j = 0; j < 3; j++) { + innerSum = innerSum + 0.1; // 3 * 0.1 = 0.3 per outer iteration + } - // State modification between inner and outer loop - innerSum = innerSum * 0.5; // Multiply by 0.5: 0.3 * 0.5 = 0.15 - total = total + innerSum; // Add to total: 2 * 0.15 = 0.3 - } + // State modification between inner and outer loop + innerSum = innerSum * 0.5; // Multiply by 0.5: 0.3 * 0.5 = 0.15 + total = total + innerSum; // Add to total: 2 * 0.15 = 0.3 + } - inputs.color = [total, total, total, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [total, total, total, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -803,18 +877,21 @@ suite('WebGPU p5.Shader', function() { test('handle for loop using loop variable in calculations', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let sum = myp5.float(0.0); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let sum = myp5.float(0.0); - for (let i = 1; i <= 3; i++) { - sum = sum + (i * 0.1); // 1*0.1 + 2*0.1 + 3*0.1 = 0.6 - } + for (let i = 1; i <= 3; i++) { + sum = sum + i * 0.1; // 1*0.1 + 2*0.1 + 3*0.1 = 0.6 + } - inputs.color = [sum, sum, sum, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [sum, sum, sum, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -831,27 +908,30 @@ suite('WebGPU p5.Shader', function() { test('handle direct StrandsFor API usage', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let accumulator = myp5.float(0.0); - - const loopResult = myp5.strandsFor( - () => 0, - (loopVar) => loopVar < 4, - (loopVar) => loopVar + 1, - (loopVar, vars) => { - let newValue = vars.accumulator.copy(); - newValue = newValue + 0.125; - return { accumulator: newValue }; - }, - { accumulator: accumulator.copy() }, - ); - - accumulator = loopResult.accumulator; - inputs.color = [accumulator, accumulator, accumulator, 1.0]; - return inputs; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let accumulator = myp5.float(0.0); + + const loopResult = myp5.strandsFor( + () => 0, + loopVar => loopVar < 4, + loopVar => loopVar + 1, + (loopVar, vars) => { + let newValue = vars.accumulator.copy(); + newValue = newValue + 0.125; + return { accumulator: newValue }; + }, + { accumulator: accumulator.copy() } + ); + + accumulator = loopResult.accumulator; + inputs.color = [accumulator, accumulator, accumulator, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -867,22 +947,25 @@ suite('WebGPU p5.Shader', function() { test('handle for loop with break statement', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseMaterialShader().modify(() => { - myp5.getPixelInputs(inputs => { - let color = 0; - let maxIterations = 5; - - for (let i = 0; i < 100; i++) { - if (i >= maxIterations) { - break; + const testShader = myp5.baseMaterialShader().modify( + () => { + myp5.getPixelInputs(inputs => { + let color = 0; + let maxIterations = 5; + + for (let i = 0; i < 100; i++) { + if (i >= maxIterations) { + break; + } + color = color + 0.1; } - color = color + 0.1; - } - inputs.color = [color, color, color, 1.0]; - return inputs; - }); - }, { myp5 }); + inputs.color = [color, color, color, 1.0]; + return inputs; + }); + }, + { myp5 } + ); myp5.noStroke(); myp5.shader(testShader); @@ -899,16 +982,19 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(50, 50, myp5.WEBGPU); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let worldPos = myp5.varyingVec3(); - myp5.getWorldInputs((inputs) => { - worldPos = inputs.position.xyz; - return inputs; - }); - myp5.getFinalColor((c) => { - return [myp5.abs(worldPos / 25), 1]; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + let worldPos = myp5.varyingVec3(); + myp5.getWorldInputs(inputs => { + worldPos = inputs.position.xyz; + return inputs; + }); + myp5.getFinalColor(c => { + return [myp5.abs(worldPos / 25), 1]; + }); + }, + { myp5 } + ); myp5.background(0, 0, 255); // Make the background blue to tell it apart myp5.noStroke(); @@ -917,13 +1003,19 @@ suite('WebGPU p5.Shader', function() { // The middle should have position 0,0 which translates to black await myp5.loadPixels(); - const midColor = myp5.pixels.slice((25 * myp5.width + 25) * 4, (25 * myp5.width + 25) * 4 + 4); + const midColor = myp5.pixels.slice( + (25 * myp5.width + 25) * 4, + (25 * myp5.width + 25) * 4 + 4 + ); assert.approximately(midColor[0], 0, 5); assert.approximately(midColor[1], 0, 5); assert.approximately(midColor[2], 0, 5); // The corner should have position 1,1 which translates to yellow - const cornerColor = myp5.pixels.slice((0 * myp5.width + 0) * 4, (0 * myp5.width + 0) * 4 + 4); + const cornerColor = myp5.pixels.slice( + (0 * myp5.width + 0) * 4, + (0 * myp5.width + 0) * 4 + 4 + ); assert.approximately(cornerColor[0], 255, 5); assert.approximately(cornerColor[1], 255, 5); assert.approximately(cornerColor[2], 0, 5); @@ -933,16 +1025,19 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(50, 50, myp5.WEBGPU); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let worldPos = myp5.varyingVec3(); - myp5.getWorldInputs((inputs) => { - worldPos.xyz = inputs.position.xyz; - return inputs; - }); - myp5.getFinalColor((c) => { - return [myp5.abs(worldPos / 25), 1]; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + let worldPos = myp5.varyingVec3(); + myp5.getWorldInputs(inputs => { + worldPos.xyz = inputs.position.xyz; + return inputs; + }); + myp5.getFinalColor(c => { + return [myp5.abs(worldPos / 25), 1]; + }); + }, + { myp5 } + ); myp5.background(0, 0, 255); // Make the background blue to tell it apart myp5.noStroke(); @@ -951,13 +1046,19 @@ suite('WebGPU p5.Shader', function() { await myp5.loadPixels(); // The middle should have position 0,0 which translates to black - const midColor = await myp5.pixels.slice((25 * myp5.width + 25) * 4, (25 * myp5.width + 25) * 4 + 4); + const midColor = await myp5.pixels.slice( + (25 * myp5.width + 25) * 4, + (25 * myp5.width + 25) * 4 + 4 + ); assert.approximately(midColor[0], 0, 5); assert.approximately(midColor[1], 0, 5); assert.approximately(midColor[2], 0, 5); // The corner should have position 1,1 which translates to yellow - const cornerColor = await myp5.pixels.slice((0 * myp5.width + 0) * 4, (0 * myp5.width + 0) * 4 + 4); + const cornerColor = await myp5.pixels.slice( + (0 * myp5.width + 0) * 4, + (0 * myp5.width + 0) * 4 + 4 + ); assert.approximately(cornerColor[0], 255, 5); assert.approximately(cornerColor[1], 255, 5); assert.approximately(cornerColor[2], 0, 5); @@ -967,17 +1068,20 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(50, 50, myp5.WEBGPU); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let worldPos = myp5.varyingVec3(); - myp5.getWorldInputs((inputs) => { - worldPos = inputs.position.xyz; - inputs.position.xyz = worldPos + [25, 25, 0]; - return inputs; - }); - myp5.getFinalColor((c) => { - return [myp5.abs(worldPos / 25), 1]; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + let worldPos = myp5.varyingVec3(); + myp5.getWorldInputs(inputs => { + worldPos = inputs.position.xyz; + inputs.position.xyz = worldPos + [25, 25, 0]; + return inputs; + }); + myp5.getFinalColor(c => { + return [myp5.abs(worldPos / 25), 1]; + }); + }, + { myp5 } + ); myp5.background(0, 0, 255); // Make the background blue to tell it apart myp5.noStroke(); @@ -986,13 +1090,19 @@ suite('WebGPU p5.Shader', function() { await myp5.loadPixels(); // The middle (shifted +25,25) should have position 0,0 which translates to black - const midColor = myp5.pixels.slice((49 * myp5.width + 49) * 4, (49 * myp5.width + 49) * 4 + 4); + const midColor = myp5.pixels.slice( + (49 * myp5.width + 49) * 4, + (49 * myp5.width + 49) * 4 + 4 + ); assert.approximately(midColor[0], 0, 5); assert.approximately(midColor[1], 0, 5); assert.approximately(midColor[2], 0, 5); // The corner (shifted +25,25) should have position 1,1 which translates to yellow - const cornerColor = myp5.pixels.slice((25 * myp5.width + 25) * 4, (25 * myp5.width + 25) * 4 + 4); + const cornerColor = myp5.pixels.slice( + (25 * myp5.width + 25) * 4, + (25 * myp5.width + 25) * 4 + 4 + ); assert.approximately(cornerColor[0], 255, 5); assert.approximately(cornerColor[1], 255, 5); assert.approximately(cornerColor[2], 0, 5); @@ -1002,17 +1112,20 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(50, 50, myp5.WEBGPU); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let processedNormal = myp5.sharedVec3(); - myp5.getPixelInputs((inputs) => { - processedNormal = myp5.normalize(inputs.normal); - return inputs; - }); - myp5.getFinalColor((c) => { - // Use the processed normal to create a color - should be [0, 0, 1] for plane facing camera - return [myp5.abs(processedNormal), 1]; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + let processedNormal = myp5.sharedVec3(); + myp5.getPixelInputs(inputs => { + processedNormal = myp5.normalize(inputs.normal); + return inputs; + }); + myp5.getFinalColor(c => { + // Use the processed normal to create a color - should be [0, 0, 1] for plane facing camera + return [myp5.abs(processedNormal), 1]; + }); + }, + { myp5 } + ); myp5.background(255, 0, 0); // Red background to distinguish from result myp5.noStroke(); @@ -1021,8 +1134,8 @@ suite('WebGPU p5.Shader', function() { // Normal of plane facing camera should be [0, 0, 1], so color should be [0, 0, 255] const centerColor = await myp5.get(25, 25); - assert.approximately(centerColor[0], 0, 5); // Red component - assert.approximately(centerColor[1], 0, 5); // Green component + assert.approximately(centerColor[0], 0, 5); // Red component + assert.approximately(centerColor[1], 0, 5); // Green component assert.approximately(centerColor[2], 255, 5); // Blue component }); @@ -1030,21 +1143,24 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(50, 50, myp5.WEBGPU); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let processedNormal = myp5.sharedVec3(); - myp5.objectInputs.begin(); - myp5.objectInputs.position += [0, 0, 0]; - myp5.objectInputs.end(); + const testShader = myp5.baseMaterialShader().modify( + () => { + let processedNormal = myp5.sharedVec3(); + myp5.objectInputs.begin(); + myp5.objectInputs.position += [0, 0, 0]; + myp5.objectInputs.end(); - myp5.pixelInputs.begin(); - processedNormal = myp5.normalize(myp5.pixelInputs.normal); - myp5.pixelInputs.end(); + myp5.pixelInputs.begin(); + processedNormal = myp5.normalize(myp5.pixelInputs.normal); + myp5.pixelInputs.end(); - myp5.finalColor.begin(); - // Use the processed normal to create a color - should be [0, 0, 1] for plane facing camera - myp5.finalColor.set([myp5.abs(processedNormal), 1]); - myp5.finalColor.end(); - }, { myp5 }); + myp5.finalColor.begin(); + // Use the processed normal to create a color - should be [0, 0, 1] for plane facing camera + myp5.finalColor.set([myp5.abs(processedNormal), 1]); + myp5.finalColor.end(); + }, + { myp5 } + ); myp5.background(255, 0, 0); // Red background to distinguish from result myp5.noStroke(); @@ -1053,8 +1169,8 @@ suite('WebGPU p5.Shader', function() { // Normal of plane facing camera should be [0, 0, 1], so color should be [0, 0, 255] const centerColor = await myp5.get(25, 25); - assert.approximately(centerColor[0], 0, 5); // Red component - assert.approximately(centerColor[1], 0, 5); // Green component + assert.approximately(centerColor[0], 0, 5); // Red component + assert.approximately(centerColor[1], 0, 5); // Green component assert.approximately(centerColor[2], 255, 5); // Blue component }); @@ -1062,16 +1178,19 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(50, 50, myp5.WEBGPU); myp5.pixelDensity(1); - const testShader = myp5.baseMaterialShader().modify(() => { - let worldPos = myp5.sharedVec3(); - myp5.getWorldInputs((inputs) => { - worldPos = inputs.position.xyz; - return inputs; - }); - myp5.getFinalColor((c) => { - return [myp5.abs(worldPos / 25), 1]; - }); - }, { myp5 }); + const testShader = myp5.baseMaterialShader().modify( + () => { + let worldPos = myp5.sharedVec3(); + myp5.getWorldInputs(inputs => { + worldPos = inputs.position.xyz; + return inputs; + }); + myp5.getFinalColor(c => { + return [myp5.abs(worldPos / 25), 1]; + }); + }, + { myp5 } + ); myp5.background(0, 0, 255); // Make the background blue to tell it apart myp5.noStroke(); @@ -1080,13 +1199,19 @@ suite('WebGPU p5.Shader', function() { await myp5.loadPixels(); // The middle should have position 0,0 which translates to black - const midColor = myp5.pixels.slice((25 * myp5.width + 25) * 4, (25 * myp5.width + 25) * 4 + 4); + const midColor = myp5.pixels.slice( + (25 * myp5.width + 25) * 4, + (25 * myp5.width + 25) * 4 + 4 + ); assert.approximately(midColor[0], 0, 5); assert.approximately(midColor[1], 0, 5); assert.approximately(midColor[2], 0, 5); // The corner should have position 1,1 which translates to yellow - const cornerColor = myp5.pixels.slice((0 * myp5.width + 0) * 4, (0 * myp5.width + 0) * 4 + 4); + const cornerColor = myp5.pixels.slice( + (0 * myp5.width + 0) * 4, + (0 * myp5.width + 0) * 4 + 4 + ); assert.approximately(cornerColor[0], 255, 5); assert.approximately(cornerColor[1], 255, 5); assert.approximately(cornerColor[2], 0, 5); @@ -1097,12 +1222,15 @@ suite('WebGPU p5.Shader', function() { test('handle getColor hook with non-struct return type', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor((inputs, canvasContent) => { - // Simple test - just return a constant color - return [1.0, 0.5, 0.0, 1.0]; // Orange color - }); - }, { myp5 }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor((inputs, canvasContent) => { + // Simple test - just return a constant color + return [1.0, 0.5, 0.0, 1.0]; // Orange color + }); + }, + { myp5 } + ); // Create a simple scene to filter myp5.background(0, 0, 255); // Blue background @@ -1114,43 +1242,54 @@ suite('WebGPU p5.Shader', function() { const pixelColor = await myp5.get(25, 25); assert.approximately(pixelColor[0], 255, 5); // Red channel should be 255 assert.approximately(pixelColor[1], 127, 5); // Green channel should be ~127 - assert.approximately(pixelColor[2], 0, 5); // Blue channel should be 0 + assert.approximately(pixelColor[2], 0, 5); // Blue channel should be 0 }); test('simple vector multiplication in filter shader', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor((inputs, canvasContent) => { - // Test simple scalar * vector operation - const scalar = 0.5; - const vector = [1, 2]; - const result = scalar * vector; - return [result.x, result.y, 0, 1]; - }); - }, { myp5 }); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor((inputs, canvasContent) => { + // Test simple scalar * vector operation + const scalar = 0.5; + const vector = [1, 2]; + const result = scalar * vector; + return [result.x, result.y, 0, 1]; + }); + }, + { myp5 } + ); }); test('handle complex filter shader with for loop and vector operations', async () => { await myp5.createCanvas(50, 50, myp5.WEBGPU); - const testShader = myp5.baseFilterShader().modify(() => { - const r = myp5.uniformFloat(() => 3); // Small value for testing - myp5.getColor((inputs, canvasContent) => { - let sum = [0, 0, 0, 0]; - let samples = 1; - - for (let i = 0; i < r; i++) { - samples++; - sum += myp5.texture(canvasContent, inputs.texCoord + (i / r) * [ - myp5.sin(4 * myp5.PI * i / r), - myp5.cos(4 * myp5.PI * i / r) - ]); - } + const testShader = myp5.baseFilterShader().modify( + () => { + const r = myp5.uniformFloat(() => 3); // Small value for testing + myp5.getColor((inputs, canvasContent) => { + let sum = [0, 0, 0, 0]; + let samples = 1; + + for (let i = 0; i < r; i++) { + samples++; + sum += myp5.texture( + canvasContent, + inputs.texCoord + + (i / r) * + [ + myp5.sin((4 * myp5.PI * i) / r), + myp5.cos((4 * myp5.PI * i) / r) + ] + ); + } - return sum / samples; - }); - }, { myp5 }); + return sum / samples; + }); + }, + { myp5 } + ); // Create a simple scene to filter myp5.background(255, 0, 0); // Red background @@ -1170,61 +1309,81 @@ suite('WebGPU p5.Shader', function() { suite('noise()', () => { for (let i = 1; i <= 3; i++) { test(`works with ${i}D vectors`, async () => { - await expect((async () => { - await myp5.createCanvas(50, 50, myp5.WEBGPU); - const input = new Array(i).fill(10); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor(() => { - return [myp5.noise(input), 0, 0, 1]; - }); - }, { myp5, input }); - myp5.shader(testShader); - myp5.plane(10, 10); - })()).resolves.not.toThrowError(); + await expect( + (async () => { + await myp5.createCanvas(50, 50, myp5.WEBGPU); + const input = new Array(i).fill(10); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor(() => { + return [myp5.noise(input), 0, 0, 1]; + }); + }, + { myp5, input } + ); + myp5.shader(testShader); + myp5.plane(10, 10); + })() + ).resolves.not.toThrowError(); }); test(`works with ${i}D positional arguments`, async () => { - await expect((async () => { - await myp5.createCanvas(50, 50, myp5.WEBGPU); - const input = new Array(i).fill(10); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor(() => { - return [myp5.noise(...input), 0, 0, 1]; - }); - }, { myp5, input }); - myp5.shader(testShader); - myp5.plane(10, 10); - })()).resolves.not.toThrowError(); + await expect( + (async () => { + await myp5.createCanvas(50, 50, myp5.WEBGPU); + const input = new Array(i).fill(10); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor(() => { + return [myp5.noise(...input), 0, 0, 1]; + }); + }, + { myp5, input } + ); + myp5.shader(testShader); + myp5.plane(10, 10); + })() + ).resolves.not.toThrowError(); }); } for (const i of [0, 4]) { test(`Does not work in ${i}D`, async () => { - await expect((async () => { - await myp5.createCanvas(50, 50, myp5.WEBGPU); - const input = new Array(i).fill(10); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor(() => { - return [myp5.noise(input), 0, 0, 1]; - }); - }, { myp5, input }); - myp5.shader(testShader); - myp5.plane(10, 10); - })()).rejects.toThrowError(); + await expect( + (async () => { + await myp5.createCanvas(50, 50, myp5.WEBGPU); + const input = new Array(i).fill(10); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor(() => { + return [myp5.noise(input), 0, 0, 1]; + }); + }, + { myp5, input } + ); + myp5.shader(testShader); + myp5.plane(10, 10); + })() + ).rejects.toThrowError(); }); test(`Does not work in ${i}D with positional arguments`, async () => { - await expect((async () => { - await myp5.createCanvas(50, 50, myp5.WEBGPU); - const input = new Array(i).fill(10); - const testShader = myp5.baseFilterShader().modify(() => { - myp5.getColor(() => { - return [myp5.noise(...input), 0, 0, 1]; - }); - }, { myp5, input }); - myp5.shader(testShader); - myp5.plane(10, 10); - })()).rejects.toThrowError(); + await expect( + (async () => { + await myp5.createCanvas(50, 50, myp5.WEBGPU); + const input = new Array(i).fill(10); + const testShader = myp5.baseFilterShader().modify( + () => { + myp5.getColor(() => { + return [myp5.noise(...input), 0, 0, 1]; + }); + }, + { myp5, input } + ); + myp5.shader(testShader); + myp5.plane(10, 10); + })() + ).rejects.toThrowError(); }); } }); @@ -1237,12 +1396,15 @@ suite('WebGPU p5.Shader', function() { // correctly handle void hooks with early returns without crashing // the strands compiler or hitting type errors. expect(() => { - const computeShader = myp5.buildComputeShader(() => { - const id = myp5.index.x; - if (id > 10) { - return; // Early return in void hook - } - }, { myp5 }); + const computeShader = myp5.buildComputeShader( + () => { + const id = myp5.index.x; + if (id > 10) { + return; // Early return in void hook + } + }, + { myp5 } + ); myp5.compute(computeShader, 1); }).not.toThrow(); @@ -1252,15 +1414,18 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(5, 5, myp5.WEBGPU); const data = myp5.createStorage([0]); - const computeShader = myp5.buildComputeShader(() => { - const buf = myp5.uniformStorage(); - const id = myp5.index.x; - if (id == 0) { - buf[0] = 1.0; - return; - buf[0] = 2.0; // Should not execute - } - }, { myp5 }); + const computeShader = myp5.buildComputeShader( + () => { + const buf = myp5.uniformStorage(); + const id = myp5.index.x; + if (id == 0) { + buf[0] = 1.0; + return; + buf[0] = 2.0; // Should not execute + } + }, + { myp5 } + ); computeShader.setUniform('buf', data); @@ -1275,14 +1440,17 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(5, 5, myp5.WEBGPU); const storage = myp5.createStorage(new Float32Array(4)); - const computeShader = myp5.buildComputeShader(() => { - const data = myp5.uniformStorage(); - function getArray() { - return [1, 2]; - } - const arr = getArray(); - data[myp5.index.x] = arr[0] + arr[1] + myp5.index.x; - }, { myp5 }); + const computeShader = myp5.buildComputeShader( + () => { + const data = myp5.uniformStorage(); + function getArray() { + return [1, 2]; + } + const arr = getArray(); + data[myp5.index.x] = arr[0] + arr[1] + myp5.index.x; + }, + { myp5 } + ); computeShader.setUniform('data', storage); @@ -1295,10 +1463,13 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(5, 5, myp5.WEBGPU); const storage = myp5.createStorage(new Float32Array(4)); - const computeShader = myp5.buildComputeShader(() => { - const data = myp5.uniformStorage(); - data[myp5.index.x] = [1, 2][0]; - }, { myp5 }); + const computeShader = myp5.buildComputeShader( + () => { + const data = myp5.uniformStorage(); + data[myp5.index.x] = [1, 2][0]; + }, + { myp5 } + ); computeShader.setUniform('data', storage); @@ -1311,11 +1482,14 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(5, 5, myp5.WEBGPU); expect(() => { - myp5.buildComputeShader(() => { - const data = myp5.uniformStorage(); - const arr = [1]; - data[myp5.index.x] = arr[0]; - }, { myp5 }); + myp5.buildComputeShader( + () => { + const data = myp5.uniformStorage(); + const arr = [1]; + data[myp5.index.x] = arr[0]; + }, + { myp5 } + ); }).toThrow('and must have 2-4 elements (got 1)'); }); @@ -1323,11 +1497,14 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(5, 5, myp5.WEBGPU); expect(() => { - myp5.buildComputeShader(() => { - const data = myp5.uniformStorage(); - const arr = [1, 2, 3, 4, 5]; - data[myp5.index.x] = arr[0]; - }, { myp5 }); + myp5.buildComputeShader( + () => { + const data = myp5.uniformStorage(); + const arr = [1, 2, 3, 4, 5]; + data[myp5.index.x] = arr[0]; + }, + { myp5 } + ); }).toThrow('and must have 2-4 elements (got 5)'); }); @@ -1336,27 +1513,36 @@ suite('WebGPU p5.Shader', function() { const storage = myp5.createStorage(new Float32Array(4)); expect(() => { - const s2 = myp5.buildComputeShader(() => { - const data = myp5.uniformStorage(); - const arr = [1, 2]; - data[myp5.index.x] = arr[0]; - }, { myp5 }); + const s2 = myp5.buildComputeShader( + () => { + const data = myp5.uniformStorage(); + const arr = [1, 2]; + data[myp5.index.x] = arr[0]; + }, + { myp5 } + ); s2.setUniform('data', storage); myp5.compute(s2, 4); - const s3 = myp5.buildComputeShader(() => { - const data = myp5.uniformStorage(); - const arr = [1, 2, 3]; - data[myp5.index.x] = arr[0]; - }, { myp5 }); + const s3 = myp5.buildComputeShader( + () => { + const data = myp5.uniformStorage(); + const arr = [1, 2, 3]; + data[myp5.index.x] = arr[0]; + }, + { myp5 } + ); s3.setUniform('data', storage); myp5.compute(s3, 4); - const s4 = myp5.buildComputeShader(() => { - const data = myp5.uniformStorage(); - const arr = [1, 2, 3, 4]; - data[myp5.index.x] = arr[0]; - }, { myp5 }); + const s4 = myp5.buildComputeShader( + () => { + const data = myp5.uniformStorage(); + const arr = [1, 2, 3, 4]; + data[myp5.index.x] = arr[0]; + }, + { myp5 } + ); s4.setUniform('data', storage); myp5.compute(s4, 4); }).not.toThrow(); @@ -1366,13 +1552,16 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(5, 5, myp5.WEBGPU); expect(() => { - myp5.baseMaterialShader().modify(() => { - let worldPosX = myp5.sharedVec3(); - myp5.getWorldInputs(inputs => { - worldPosX = inputs.position.x; // scalar → vec3, valid broadcast - return inputs; - }); - }, { myp5 }); + myp5.baseMaterialShader().modify( + () => { + let worldPosX = myp5.sharedVec3(); + myp5.getWorldInputs(inputs => { + worldPosX = inputs.position.x; // scalar → vec3, valid broadcast + return inputs; + }); + }, + { myp5 } + ); }).not.toThrow(); }); @@ -1380,13 +1569,16 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(5, 5, myp5.WEBGPU); expect(() => { - myp5.baseMaterialShader().modify(() => { - let myVec = myp5.sharedVec3(); - myp5.getWorldInputs(inputs => { - myVec = inputs.position.xy; // vec2 → vec3 mismatch - return inputs; - }); - }, { myp5 }); + myp5.baseMaterialShader().modify( + () => { + let myVec = myp5.sharedVec3(); + myp5.getWorldInputs(inputs => { + myVec = inputs.position.xy; // vec2 → vec3 mismatch + return inputs; + }); + }, + { myp5 } + ); }).toThrow(/dimension mismatch/); }); @@ -1394,13 +1586,16 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(5, 5, myp5.WEBGPU); expect(() => { - myp5.baseMaterialShader().modify(() => { - let myVec = myp5.sharedVec3(); - myp5.getWorldInputs(inputs => { - myVec.xy = inputs.position; // vec3 → 2-component swizzle mismatch - return inputs; - }); - },{myp5}); + myp5.baseMaterialShader().modify( + () => { + let myVec = myp5.sharedVec3(); + myp5.getWorldInputs(inputs => { + myVec.xy = inputs.position; // vec3 → 2-component swizzle mismatch + return inputs; + }); + }, + { myp5 } + ); }).toThrow(/dimension mismatch/); }); @@ -1408,13 +1603,16 @@ suite('WebGPU p5.Shader', function() { await myp5.createCanvas(5, 5, myp5.WEBGPU); expect(() => { - myp5.baseMaterialShader().modify(() => { - let myVec = myp5.sharedVec3(); - myp5.getWorldInputs(inputs => { - myVec = inputs.position; // vec3 → vec3, OK - return inputs; - }); - },{myp5}); + myp5.baseMaterialShader().modify( + () => { + let myVec = myp5.sharedVec3(); + myp5.getWorldInputs(inputs => { + myVec = inputs.position; // vec3 → vec3, OK + return inputs; + }); + }, + { myp5 } + ); }).not.toThrow(); }); }); diff --git a/translations/dev.js b/translations/dev.js index 08651b2a76..e30da4ed42 100644 --- a/translations/dev.js +++ b/translations/dev.js @@ -16,4 +16,4 @@ export { default as ja_translation } from './ja/translation.json'; * * "es_MX" is the language key whereas "translation" is the filename * ( translation.json ) or the namespace -*/ + */ diff --git a/translations/index.js b/translations/index.js index a04c06d1d9..591f918248 100644 --- a/translations/index.js +++ b/translations/index.js @@ -22,11 +22,4 @@ export default { * dev.js, which is another file in this folder. * @private */ -export const languages = [ - 'en', - 'es', - 'ko', - 'zh', - 'hi', - 'ja' -]; +export const languages = ['en', 'es', 'ko', 'zh', 'hi', 'ja']; diff --git a/translations/ko/README.md b/translations/ko/README.md index c15844116f..aad3b0225a 100644 --- a/translations/ko/README.md +++ b/translations/ko/README.md @@ -1,26 +1,29 @@ # Welcome to the FES Korean branch! + 안녕하세요, FES 한국어 브랜치에 어서오세요! ## 한국어 공동 번역 기여자 Korean Translation Credits + 2021년 가을부터 공동작업으로 진행되어 2022년 1월에 마무리된 FES 에러메시지 공동 번역 작업은 아래 분들이 함께하셨습니다. -* [염인화](https://yinhwa.art/) (Inhwa Yeom): artist/XR researcher based in South Korea. (Take a look at her works on [p5 for 50+](https://p5for50.plus/) ([Processing Foundation Fellows 2020](https://medium.com/processing-foundation/p5-js-for-ages-50-in-korea-50d47b5927fb)) and p5js website Korean translation) -* 전유진 (Youjin Jeon): artist/organizer based in Seoul, South Korea. [여성을 위한 열린 기술랩(Woman Open Tech Lab.kr)](http://womanopentechlab.kr/) and [Seoul Express](http://seoulexpress.kr/) -* [정앎](https://www.almichu.com/) (Alm Chung, organizer): Korean-American artist/researcher based in Seattle, WA. -* 이지현 (Jihyun Lee): Korean publishing editor based in South Korea + +- [염인화](https://yinhwa.art/) (Inhwa Yeom): artist/XR researcher based in South Korea. (Take a look at her works on [p5 for 50+](https://p5for50.plus/) ([Processing Foundation Fellows 2020](https://medium.com/processing-foundation/p5-js-for-ages-50-in-korea-50d47b5927fb)) and p5js website Korean translation) +- 전유진 (Youjin Jeon): artist/organizer based in Seoul, South Korea. [여성을 위한 열린 기술랩(Woman Open Tech Lab.kr)](http://womanopentechlab.kr/) and [Seoul Express](http://seoulexpress.kr/) +- [정앎](https://www.almichu.com/) (Alm Chung, organizer): Korean-American artist/researcher based in Seattle, WA. +- 이지현 (Jihyun Lee): Korean publishing editor based in South Korea ## 영한 번역 리소스 (Korean-English Translation Resources) -* 영한 [번역에 도움이 되는 툴과 유의점들]입니다. -* 또한 영한 [번역 작업 중 마주치는 딜레마들] 속에서 저희가 채택한 방식을 모아 적어봤습니다. -* 외래 [기술 용어 다루기]에 대한 논의입니다. -* p5js 웹사이트와 기술 문서에서 사용하는 기술 용어들을 통일하기위해 사용하고 있 [p5js.org/ko 기술 용어 색인] 입니다. -* 현존하는 검색툴/번역 툴들과 연계 가능한 "[사이를 맴도는]" 번역문에 대해 생각해보는 글입니다. -이 외에도 FES의 세계화 작업 과정, 그리고 과정 중 논의된 이슈들을 [Friendly Errors i18n Book ✎ 친절한 오류 메시지 세계화 가이드북]에서 읽어보실 수 있습니다. "친절한 오류 메시지 세계화 가이드북"은 오픈 소스 프로젝트이며, 이 [독립된 레파지토리 (repository)]를 통해 기여 가능합니다. +- 영한 [번역에 도움이 되는 툴과 유의점들]입니다. +- 또한 영한 [번역 작업 중 마주치는 딜레마들] 속에서 저희가 채택한 방식을 모아 적어봤습니다. +- 외래 [기술 용어 다루기]에 대한 논의입니다. +- p5js 웹사이트와 기술 문서에서 사용하는 기술 용어들을 통일하기위해 사용하고 있 [p5js.org/ko 기술 용어 색인] 입니다. +- 현존하는 검색툴/번역 툴들과 연계 가능한 "[사이를 맴도는]" 번역문에 대해 생각해보는 글입니다. +이 외에도 FES의 세계화 작업 과정, 그리고 과정 중 논의된 이슈들을 [Friendly Errors i18n Book ✎ 친절한 오류 메시지 세계화 가이드북]에서 읽어보실 수 있습니다. "친절한 오류 메시지 세계화 가이드북"은 오픈 소스 프로젝트이며, 이 [독립된 레파지토리 (repository)]를 통해 기여 가능합니다. [번역에 도움이 되는 툴과 유의점들]: https://almchung.github.io/p5-fes-i18n-book/ch4/#tools [번역 작업 중 마주치는 딜레마들]: https://almchung.github.io/p5-fes-i18n-book/ch4/#dilemmas [기술 용어 다루기]: https://almchung.github.io/p5-fes-i18n-book/ch3/ [사이를 맴도는]: https://almchung.github.io/p5-fes-i18n-book/ch5/ [Friendly Errors i18n Book ✎ 친절한 오류 메시지 세계화 가이드북]: https://almchung.github.io/p5-fes-i18n-book/ -[독립된 레파지토리 (repository)]: https://github.com/almchung/p5-fes-i18n-book \ No newline at end of file +[독립된 레파지토리 (repository)]: https://github.com/almchung/p5-fes-i18n-book diff --git a/utils/convert.mjs b/utils/convert.mjs index 9e874259ce..2e3219caf3 100644 --- a/utils/convert.mjs +++ b/utils/convert.mjs @@ -7,23 +7,46 @@ import { descriptionString, typeObject } from './shared-helpers.mjs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const data = JSON.parse(fs.readFileSync(path.join(__dirname, '../docs/data.json'))); +const data = JSON.parse( + fs.readFileSync(path.join(__dirname, '../docs/data.json')) +); // Generate documentation used in the p5.js reference. This data will get read in // the p5.js-website repo: https://github.com/processing/p5.js-website/ const htmlStrategy = { - shouldSkipEntry: (entry) => + shouldSkipEntry: entry => // Skip static methods on p5.Vector for now because the names clash with // the non static versions entry.scope === 'static' && entry.memberof === 'Vector' && - ['add', 'sub', 'mult', 'div', 'copy', 'rem', 'rotate', 'dot', 'cross', 'dist', 'lerp', 'slerp', - 'mag', 'magSq', 'normalize', 'limit', 'setMag', 'heading', 'angleBetween', 'reflect', - 'array', 'equals'].includes(entry.name), - - processDescription: (desc) => descriptionString(desc), - - processType: (type) => typeObject(type) + [ + 'add', + 'sub', + 'mult', + 'div', + 'copy', + 'rem', + 'rotate', + 'dot', + 'cross', + 'dist', + 'lerp', + 'slerp', + 'mag', + 'magSq', + 'normalize', + 'limit', + 'setMag', + 'heading', + 'angleBetween', + 'reflect', + 'array', + 'equals' + ].includes(entry.name), + + processDescription: desc => descriptionString(desc), + + processType: type => typeObject(type) }; const processed = processData(data, htmlStrategy); @@ -38,7 +61,6 @@ const converted = { consts: processed.consts }; - // Register constant usage for the original convert.mjs functionality const constUsage = {}; function registerConstantUsage(name, memberof, node) { @@ -64,7 +86,12 @@ function registerConstantUsage(name, memberof, node) { // Register constant usage from processed data for (const item of converted.classitems) { - if (item.itemtype === 'property' && (item.name in converted.consts || item.kind === 'constant' || item.kind === 'typedef')) { + if ( + item.itemtype === 'property' && + (item.name in converted.consts || + item.kind === 'constant' || + item.kind === 'typedef') + ) { constUsage[item.name] = constUsage[item.name] || new Set(); } if (item.itemtype === 'method') { @@ -84,7 +111,6 @@ for (const key in constUsage) { converted.consts[key] = [...constUsage[key]]; } - // ============================================================================ // parameterData.json // ============================================================================ @@ -173,7 +199,11 @@ function buildParamDocs(docs) { for (let classitem of docs.classitems) { // If `classitem` doesn't have overloads, then it's not a function—skip processing in this case - if (classitem.name && classitem.class && classitem.hasOwnProperty('overloads')) { + if ( + classitem.name && + classitem.class && + classitem.hasOwnProperty('overloads') + ) { // Skip if the item already exists in newClassItems if ( newClassItems[classitem.class] && @@ -215,7 +245,12 @@ function buildParamDocs(docs) { } fs.mkdirSync(path.join(__dirname, '../docs/reference'), { recursive: true }); -fs.writeFileSync(path.join(__dirname, '../docs/reference/data.json'), JSON.stringify(converted, null, 2)); -fs.writeFileSync(path.join(__dirname, '../docs/reference/data.min.json'), JSON.stringify(converted)); +fs.writeFileSync( + path.join(__dirname, '../docs/reference/data.json'), + JSON.stringify(converted, null, 2) +); +fs.writeFileSync( + path.join(__dirname, '../docs/reference/data.min.json'), + JSON.stringify(converted) +); buildParamDocs(JSON.parse(JSON.stringify(converted))); - diff --git a/utils/data-processor.mjs b/utils/data-processor.mjs index 7349e10773..9fc72a20e8 100644 --- a/utils/data-processor.mjs +++ b/utils/data-processor.mjs @@ -8,7 +8,7 @@ import { getParams } from './shared-helpers.mjs'; export function processData(rawData, strategy) { const allData = getAllEntries(rawData); - + const processed = { modules: {}, classes: {}, @@ -21,7 +21,7 @@ export function processData(rawData, strategy) { const fileModuleInfo = {}; const modules = {}; const submodules = {}; - + for (const entry of allData) { if (entry.tags?.some(tag => tag.title === 'module')) { const module = entry.tags.find(tag => tag.title === 'module').name; @@ -37,7 +37,8 @@ export function processData(rawData, strategy) { for: undefined }; fileModuleInfo[file].module = module; - fileModuleInfo[file].submodule = fileModuleInfo[file].submodule || submodule; + fileModuleInfo[file].submodule = + fileModuleInfo[file].submodule || submodule; fileModuleInfo[file].for = fileModuleInfo[file].for || forEntry; modules[module] = modules[module] || { @@ -70,8 +71,11 @@ export function processData(rawData, strategy) { const entryForTagValue = entryForTag?.description; const file = entry.context?.file; let { module, submodule, for: forEntry } = fileModuleInfo[file] || {}; - module = entry.tags?.find(tag => tag.title === 'module')?.description || module; - submodule = entry.tags?.find(tag => tag.title === 'submodule')?.description || submodule; + module = + entry.tags?.find(tag => tag.title === 'module')?.description || module; + submodule = + entry.tags?.find(tag => tag.title === 'submodule')?.description || + submodule; let memberof = entry.memberof; if (memberof === 'fn') memberof = 'p5'; if (memberof && memberof !== 'p5' && !memberof.startsWith('p5.')) { @@ -83,7 +87,9 @@ export function processData(rawData, strategy) { function locationInfo(entry) { return { - file: entry.context?.file ? entry.context.file.slice(entry.context.file.indexOf('src/')) : '', + file: entry.context?.file + ? entry.context.file.slice(entry.context.file.indexOf('src/')) + : '', line: entry.context?.loc?.start?.line || 1 }; } @@ -103,37 +109,47 @@ export function processData(rawData, strategy) { } function getAlt(entry) { - return entry - .tags - ?.filter(tag => tag.title === 'alt') - ?.map(tag => tag.description) - ?.join('\n') || undefined; + return ( + entry.tags + ?.filter(tag => tag.title === 'alt') + ?.map(tag => tag.description) + ?.join('\n') || undefined + ); } // Process constants, typedefs, and properties const processedNames = new Set(); for (const entry of allData) { - if (entry.kind === 'constant' || entry.kind === 'typedef' || entry.kind === 'property' || - (entry.properties && entry.properties.length > 0 && entry.properties[0].title === 'property') || - entry.tags?.some(tag => tag.title === 'property')) { + if ( + entry.kind === 'constant' || + entry.kind === 'typedef' || + entry.kind === 'property' || + (entry.properties && + entry.properties.length > 0 && + entry.properties[0].title === 'property') || + entry.tags?.some(tag => tag.title === 'property') + ) { const { module, submodule, forEntry } = getModuleInfo(entry); - + // Apply strategy filter - if (strategy.shouldSkipEntry && strategy.shouldSkipEntry(entry, { module, submodule, forEntry })) { + if ( + strategy.shouldSkipEntry && + strategy.shouldSkipEntry(entry, { module, submodule, forEntry }) + ) { continue; } - const name = entry.name || + const name = + entry.name || (entry.properties || [])[0]?.name || entry.tags?.find(t => t.title === 'property')?.name; - + // Skip duplicates based on name + class combination const key = `${name}:${forEntry || 'p5'}`; if (processedNames.has(key)) { continue; } processedNames.add(key); - // For properties, get type from the property definition const propertyType = entry.properties?.[0]?.type || entry.type; @@ -153,7 +169,7 @@ export function processData(rawData, strategy) { class: forEntry || 'p5', beta: entry.tags?.some(t => t.title === 'beta') || undefined, webgpu: entry.tags?.some(t => t.title === 'webgpu') || undefined, - webgpuOnly: entry.tags?.some(t => t.title === 'webgpuOnly') || undefined, + webgpuOnly: entry.tags?.some(t => t.title === 'webgpuOnly') || undefined }; processed.classitems.push(item); @@ -165,9 +181,12 @@ export function processData(rawData, strategy) { for (const entry of allData) { if (entry.kind === 'class') { const { module, submodule } = getModuleInfo(entry); - + // Apply strategy filter - if (strategy.shouldSkipEntry && strategy.shouldSkipEntry(entry, { module, submodule })) { + if ( + strategy.shouldSkipEntry && + strategy.shouldSkipEntry(entry, { module, submodule }) + ) { continue; } @@ -181,11 +200,14 @@ export function processData(rawData, strategy) { alt: getAlt(entry), params: getParams(entry).map(p => ({ name: p.name, - description: p.description && strategy.processDescription(p.description), + description: + p.description && strategy.processDescription(p.description), ...strategy.processType(p.type, p) })), return: entry.returns?.[0] && { - description: strategy.processDescription(entry.returns[0].description), + description: strategy.processDescription( + entry.returns[0].description + ), ...strategy.processType(entry.returns[0].type) }, is_constructor: 1, @@ -193,7 +215,7 @@ export function processData(rawData, strategy) { submodule, beta: entry.tags?.some(t => t.title === 'beta') || undefined, webgpu: entry.tags?.some(t => t.title === 'webgpu') || undefined, - webgpuOnly: entry.tags?.some(t => t.title === 'webgpuOnly') || undefined, + webgpuOnly: entry.tags?.some(t => t.title === 'webgpuOnly') || undefined }; // The @private tag doesn't seem to end up in the Documentation.js output. @@ -211,9 +233,12 @@ export function processData(rawData, strategy) { for (const entry of allData) { if (entry.kind === 'function' && entry.properties?.length === 0) { const { module, submodule, forEntry } = getModuleInfo(entry); - + // Apply strategy filter - if (strategy.shouldSkipEntry && strategy.shouldSkipEntry(entry, { module, submodule, forEntry })) { + if ( + strategy.shouldSkipEntry && + strategy.shouldSkipEntry(entry, { module, submodule, forEntry }) + ) { continue; } @@ -227,7 +252,7 @@ export function processData(rawData, strategy) { } const className = memberof || forEntry || 'p5'; - + // Skip private methods if (entry.name.startsWith('_')) continue; @@ -244,10 +269,12 @@ export function processData(rawData, strategy) { ...locationInfo(entry), ...deprecationInfo(entry), itemtype: 'method', - description: prevItem?.description || strategy.processDescription(entry.description), + description: + prevItem?.description || + strategy.processDescription(entry.description), example: [ ...(prevItem?.example || []), - ...entry.examples?.map(getExample) || [] + ...(entry.examples?.map(getExample) || []) ], alt: getAlt(entry), overloads: [ @@ -255,30 +282,49 @@ export function processData(rawData, strategy) { { params: getParams(entry).map(p => ({ name: p.name, - description: p.description && strategy.processDescription(p.description), + description: + p.description && strategy.processDescription(p.description), ...strategy.processType(p.type, p) })), return: entry.returns?.[0] && { - description: strategy.processDescription(entry.returns[0].description), + description: strategy.processDescription( + entry.returns[0].description + ), ...strategy.processType(entry.returns[0].type) }, - chainable: entry.tags?.some(tag => tag.title === 'chainable') ? 1 : undefined + chainable: entry.tags?.some(tag => tag.title === 'chainable') + ? 1 + : undefined } ], - return: prevItem?.return || entry.returns?.[0] && { - description: strategy.processDescription(entry.returns[0].description), - ...strategy.processType(entry.returns[0].type) - }, + return: + prevItem?.return || + (entry.returns?.[0] && { + description: strategy.processDescription( + entry.returns[0].description + ), + ...strategy.processType(entry.returns[0].type) + }), class: className, static: entry.scope === 'static' && 1, module: prevItem?.module ?? module, submodule: prevItem?.submodule ?? submodule, - beta: prevItem?.beta || entry.tags?.some(t => t.title === 'beta') || undefined, - webgpu: prevItem?.webgpu || entry.tags?.some(t => t.title === 'webgpu') || undefined, - webgpuOnly: prevItem?.webgpuOnly || entry.tags?.some(t => t.title === 'webgpuOnly') || undefined, + beta: + prevItem?.beta || + entry.tags?.some(t => t.title === 'beta') || + undefined, + webgpu: + prevItem?.webgpu || + entry.tags?.some(t => t.title === 'webgpu') || + undefined, + webgpuOnly: + prevItem?.webgpuOnly || + entry.tags?.some(t => t.title === 'webgpuOnly') || + undefined }; - processed.classMethods[className] = processed.classMethods[className] || {}; + processed.classMethods[className] = + processed.classMethods[className] || {}; processed.classMethods[className][methodKey] = item; } } @@ -291,4 +337,4 @@ export function processData(rawData, strategy) { } return processed; -} \ No newline at end of file +} diff --git a/utils/patch.mjs b/utils/patch.mjs index 841ac9c703..dc3e1611f0 100644 --- a/utils/patch.mjs +++ b/utils/patch.mjs @@ -1,26 +1,24 @@ import fs from 'fs'; export function applyPatches() { - const cache = {}; + const cache = {}; const patched = {}; - + const replace = (path, src, dest) => { if (Array.isArray(path)) { path.forEach(path => replace(path, src, dest)); return; } try { - if (!path.startsWith("types/")) - path = "types/" + path; + if (!path.startsWith('types/')) path = 'types/' + path; - const before = patched[path] ?? - (cache[path] ??= fs.readFileSync("./" + path, { encoding: 'utf-8' })); + const before = + patched[path] ?? + (cache[path] ??= fs.readFileSync('./' + path, { encoding: 'utf-8' })); const after = before.replaceAll(src, dest); - if (after !== before) - patched[path] = after; - else - console.error(`A patch failed in ${path}:\n -${src}\n +${dest}`); + if (after !== before) patched[path] = after; + else console.error(`A patch failed in ${path}:\n -${src}\n +${dest}`); } catch (err) { console.error(err); } @@ -28,8 +26,8 @@ export function applyPatches() { // TODO: Handle this better in the docs instead of patching replace( - "p5.d.ts", - "constructor(detailX?: number, detailY?: number, callback?: Function);", + 'p5.d.ts', + 'constructor(detailX?: number, detailY?: number, callback?: Function);', `constructor( detailX?: number, detailY?: number, @@ -39,9 +37,9 @@ export function applyPatches() { // https://github.com/p5-types/p5.ts/issues/31 // #todo: add readonly to appropriate array params, either here or in doc comments replace( - ["p5.d.ts", "global.d.ts"], - "random(choices: any[]): any;", - "random(choices: readonly T[]): T;" + ['p5.d.ts', 'global.d.ts'], + 'random(choices: any[]): any;', + 'random(choices: readonly T[]): T;' ); replace( @@ -53,7 +51,7 @@ export function applyPatches() { replace( 'p5.d.ts', 'textToContours(str: string, x: number, y: number, options?: { sampleFactor?: number; simplifyThreshold?: number }): object[][];', - 'textToContours(str: string, x: number, y: number, options?: { sampleFactor?: number; simplifyThreshold?: number }): { x: number; y: number; alpha: number }[][];', + 'textToContours(str: string, x: number, y: number, options?: { sampleFactor?: number; simplifyThreshold?: number }): { x: number; y: number; alpha: number }[][];' ); replace( @@ -77,7 +75,7 @@ export function applyPatches() { 'class __Graphics extends p5.Element {', `class __Graphics extends p5.Element { elt: HTMLCanvasElement; - `, + ` ); // Type .elt more specifically for audio and video elements @@ -86,24 +84,24 @@ export function applyPatches() { `class MediaElement extends Element { elt: HTMLAudioElement | HTMLVideoElement;`, `class MediaElement extends Element { - elt: T;`, + elt: T;` ); replace( ['p5.d.ts', 'global.d.ts'], /createAudio\(src\?: string \| string\[\], callback\?: Function\): ([pP]5)\.MediaElement;/g, - 'createAudio(src?: string | string[], callback?: (video: $1.MediaElement) => any): $1.MediaElement;', + 'createAudio(src?: string | string[], callback?: (video: $1.MediaElement) => any): $1.MediaElement;' ); replace( ['p5.d.ts', 'global.d.ts'], /createVideo\(src\?: string \| string\[\], callback\?: Function\): ([pP]5)\.MediaElement;/g, - 'createVideo(src?: string | string[], callback?: (video: $1.MediaElement) => any): $1.MediaElement;', + 'createVideo(src?: string | string[], callback?: (video: $1.MediaElement) => any): $1.MediaElement;' ); // More callback types replace( ['p5.d.ts', 'global.d.ts'], /createFileInput\(callback: Function, multiple\?: boolean\): ([pP]5)\.Element;/g, - 'createFileInput(callback: (input: $1.File) => any, multiple?: boolean): $1.Element;', + 'createFileInput(callback: (input: $1.File) => any, multiple?: boolean): $1.Element;' ); replace( ['p5.d.ts', 'global.d.ts'], @@ -120,7 +118,7 @@ export function applyPatches() { replace( 'p5.d.ts', 'fontBounds(str: string, x: number, y: number, width?: number, height?: number): object;', - 'fontBounds(str: string, x: number, y: number, width?: number, height?: number): { x: number; y: number; w: number; h: number };', + 'fontBounds(str: string, x: number, y: number, width?: number, height?: number): { x: number; y: number; w: number; h: number };' ); replace( 'p5.d.ts', @@ -130,7 +128,7 @@ export function applyPatches() { replace( 'p5.d.ts', 'textBounds(str: string, x: number, y: number, width?: number, height?: number): object;', - 'textBounds(str: string, x: number, y: number, width?: number, height?: number): { x: number; y: number; w: number; h: number };', + 'textBounds(str: string, x: number, y: number, width?: number, height?: number): { x: number; y: number; w: number; h: number };' ); // Document Typr @@ -169,7 +167,7 @@ export function applyPatches() { for (const [path, data] of Object.entries(patched)) { try { console.log(`Patched ${path}`); - fs.writeFileSync("./" + path, data); + fs.writeFileSync('./' + path, data); } catch (err) { console.error(err); } diff --git a/utils/sample-linter.mjs b/utils/sample-linter.mjs index 08b4fb94e1..64d64548f2 100644 --- a/utils/sample-linter.mjs +++ b/utils/sample-linter.mjs @@ -1,7 +1,7 @@ 'use strict'; const EOL = '\n'; import { ESLint } from 'eslint'; -import dataDoc from '../docs/reference/data.min.json' assert {type: 'json'}; +import dataDoc from '../docs/reference/data.min.json' assert { type: 'json' }; // envs: ['eslint-samples/p5'], const itemtypes = ['method', 'property']; @@ -9,9 +9,7 @@ const classes = ['p5']; const globals = {}; dataDoc.classitems - .filter( - ci => classes.includes(ci.class) && itemtypes.includes(ci.itemtype) - ) + .filter(ci => classes.includes(ci.class) && itemtypes.includes(ci.itemtype)) .forEach(ci => { globals[ci.name] = true; }); @@ -63,7 +61,7 @@ const plugin = { processors: { '.js': { supportsAutofix: true, - preprocess: function(text) { + preprocess: function (text) { globalLines = splitLines(text); let m; @@ -101,7 +99,7 @@ const plugin = { return globalSamples.map(s => s.code + userFunctionTrailer); }, - postprocess: function(sampleMessages, filename) { + postprocess: function (sampleMessages, filename) { const problems = []; for (let i = 0; i < sampleMessages.length; i++) { @@ -177,7 +175,7 @@ async function eslintFiles(opts, filesSrc) { 'eslint-samples': plugin }, overrideConfig: { - 'plugins': ['eslint-samples'] + plugins: ['eslint-samples'] }, fix: opts.fix }); @@ -194,18 +192,21 @@ async function eslintFiles(opts, filesSrc) { } let results = await eslint.lintFiles(filesSrc); - const report = results.reduce((acc, result) => { - acc.errorCount += result.errorCount; - acc.warningCount += result.warningCount; - acc.fixableErrorCount += result.fixableErrorCount; - acc.fixableWarningCount += result.fixableWarningCount; - return acc; - }, { - errorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }); + const report = results.reduce( + (acc, result) => { + acc.errorCount += result.errorCount; + acc.warningCount += result.warningCount; + acc.fixableErrorCount += result.fixableErrorCount; + acc.fixableWarningCount += result.fixableWarningCount; + return acc; + }, + { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 + } + ); if (opts.quiet) { results = ESLint.getErrorResults(results); @@ -220,7 +221,7 @@ async function eslintFiles(opts, filesSrc) { function splitLines(text) { const lines = []; - lines.lineFromIndex = function(index) { + lines.lineFromIndex = function (index) { const lines = this; const lineCount = lines.length; for (let i = 0; i < lineCount; i++) { @@ -246,9 +247,8 @@ function splitLines(text) { return lines; } -eslintFiles(null, process.argv.slice(2)) - .then(result => { - if(result === true) process.exit(0); - console.log(result.output); - process.exit(result.report.errorCount === 0 ? 0 : 1); - }); +eslintFiles(null, process.argv.slice(2)).then(result => { + if (result === true) process.exit(0); + console.log(result.output); + process.exit(result.report.errorCount === 0 ? 0 : 1); +}); diff --git a/utils/shared-helpers.mjs b/utils/shared-helpers.mjs index e8fb5791bb..b13d2b26f6 100644 --- a/utils/shared-helpers.mjs +++ b/utils/shared-helpers.mjs @@ -12,7 +12,7 @@ function getEntries(entry) { } export function getAllEntries(arr = []) { - return arr.flatMap(entry => entry ? getEntries(entry) : []); + return arr.flatMap(entry => (entry ? getEntries(entry) : [])); } export function descriptionString(node, parent) { @@ -34,16 +34,24 @@ export function descriptionString(node, parent) { classes.push(node.meta); } if (classes.length > 0) { - attrs=` class="${classes.join(' ')}"`; + attrs = ` class="${classes.join(' ')}"`; } return `
    `; } else if (node.type === 'inlineCode') { return '' + node.value + ''; } else if (node.type === 'list') { const tag = node.type === 'ordered' ? 'ol' : 'ul'; - return `<${tag}>` + node.children.map(n => descriptionString(n, node)).join('') + ``; + return ( + `<${tag}>` + + node.children.map(n => descriptionString(n, node)).join('') + + `` + ); } else if (node.type === 'listItem') { - return '
  10. ' + node.children.map(n => descriptionString(n, node)).join('') + '
  11. '; + return ( + '
  12. ' + + node.children.map(n => descriptionString(n, node)).join('') + + '
  13. ' + ); } else if (node.value) { return node.value; } else if (node.children) { @@ -60,20 +68,31 @@ export function descriptionStringForTypeScript(node, parent) { } else if (node.type === 'text') { return node.value; } else if (node.type === 'paragraph') { - const content = node.children.map(n => descriptionStringForTypeScript(n, node)).join(''); + const content = node.children + .map(n => descriptionStringForTypeScript(n, node)) + .join(''); return content + '\n\n'; // Skip HTML tags for TypeScript } else if (node.type === 'code') { return `\`${node.value}\``; } else if (node.type === 'inlineCode') { return `\`${node.value}\``; } else if (node.type === 'list') { - return node.children.map(n => descriptionStringForTypeScript(n, node)).join('') + '\n'; + return ( + node.children.map(n => descriptionStringForTypeScript(n, node)).join('') + + '\n' + ); } else if (node.type === 'listItem') { - return '- ' + node.children.map(n => descriptionStringForTypeScript(n, node)).join('') + '\n'; + return ( + '- ' + + node.children.map(n => descriptionStringForTypeScript(n, node)).join('') + + '\n' + ); } else if (node.value) { return node.value; } else if (node.children) { - return node.children.map(n => descriptionStringForTypeScript(n, node)).join(''); + return node.children + .map(n => descriptionStringForTypeScript(n, node)) + .join(''); } else { return ''; } @@ -91,10 +110,7 @@ export function typeObject(node) { }; } else if (node.type === 'TypeApplication') { const { type: typeName } = typeObject(node.expression); - if ( - typeName === 'Array' && - node.applications.length === 1 - ) { + if (typeName === 'Array' && node.applications.length === 1) { return { type: `${typeObject(node.applications[0]).type}[]` }; @@ -112,7 +128,9 @@ export function typeObject(node) { } return { type: signature }; } else if (node.type === 'ArrayType') { - return { type: `[${node.elements.map(e => typeObject(e).type).join(', ')}]` }; + return { + type: `[${node.elements.map(e => typeObject(e).type).join(', ')}]` + }; } else if (node.type === 'RestType') { return { type: typeObject(node.expression).type, rest: true }; } else { @@ -132,22 +150,25 @@ export function getParams(entry) { // instead convert it to a string. We want a slightly different conversion to // string, so we match these params to the Documentation.js-provided `params` // array and grab the description from those. - return (entry.tags || []) + return ( + (entry.tags || []) - // Filter out the nested parameters (eg. options.extrude), - // to be treated as part of parent parameters (eg. options) - // and not separate entries - .filter(t => t.title === 'param' && !t.name.includes('.')) - .map(node => { - const param = (entry.params || []) - .find(param => param.name === node.name); - return { - ...node, - description: param?.description || { - type: 'html', - value: node.description - }, - properties: param?.properties // Preserve properties array for nested object parameters - }; - }); -} \ No newline at end of file + // Filter out the nested parameters (eg. options.extrude), + // to be treated as part of parent parameters (eg. options) + // and not separate entries + .filter(t => t.title === 'param' && !t.name.includes('.')) + .map(node => { + const param = (entry.params || []).find( + param => param.name === node.name + ); + return { + ...node, + description: param?.description || { + type: 'html', + value: node.description + }, + properties: param?.properties // Preserve properties array for nested object parameters + }; + }) + ); +} diff --git a/utils/typescript.mjs b/utils/typescript.mjs index 751ff117f8..dd9b7184b8 100644 --- a/utils/typescript.mjs +++ b/utils/typescript.mjs @@ -17,7 +17,9 @@ if (fs.existsSync(typesDir)) { } fs.mkdirSync(typesDir, { recursive: true }); -const rawData = JSON.parse(fs.readFileSync(path.join(__dirname, '../docs/data.json'))); +const rawData = JSON.parse( + fs.readFileSync(path.join(__dirname, '../docs/data.json')) +); // Pre-build constants lookup import { getAllEntries } from './shared-helpers.mjs'; @@ -40,47 +42,55 @@ function processStrandsFunctions() { const strandsMethods = [ { name: 'instanceID', - overloads: [{ - params: [], - return: { - type: { type: 'NameExpression', name: 'any' } // Return 'any' for strands nodes + overloads: [ + { + params: [], + return: { + type: { type: 'NameExpression', name: 'any' } // Return 'any' for strands nodes + } } - }], + ], description: `Returns the ID when drawing many instances`, static: false }, { name: 'discard', - overloads: [{ - params: [], - }], + overloads: [ + { + params: [] + } + ], description: `Discards the current pixel`, static: false }, { name: 'getTexture', - overloads: [{ - params: [ - { - name: 'tex', - type: { type: 'NameExpression', name: 'any' }, - optional: false, - }, - { - name: 'coord', - type: { type: 'NameExpression', name: 'any' }, - optional: false, - }, - ], - return: { - type: { type: 'NameExpression', name: 'any' } // Return 'any' for strands nodes + overloads: [ + { + params: [ + { + name: 'tex', + type: { type: 'NameExpression', name: 'any' }, + optional: false + }, + { + name: 'coord', + type: { type: 'NameExpression', name: 'any' }, + optional: false + } + ], + return: { + type: { type: 'NameExpression', name: 'any' } // Return 'any' for strands nodes + } } - }], + ] } ]; // Add ALL GLSL builtin functions (both isp5Function: true and false) - for (const [functionName, overloads] of Object.entries(builtInGLSLFunctions)) { + for (const [functionName, overloads] of Object.entries( + builtInGLSLFunctions + )) { // Create method definition with simplified any types for all overloads const method = { name: functionName, @@ -113,52 +123,56 @@ function processStrandsFunctions() { const typeAliases = []; if (/^[ib]vec/.test(typeInfo.fnName)) { - pascalTypeName = typeInfo.fnName - .slice(0, 2).toUpperCase() - + typeInfo.fnName - .slice(2) - .toLowerCase(); + pascalTypeName = + typeInfo.fnName.slice(0, 2).toUpperCase() + + typeInfo.fnName.slice(2).toLowerCase(); typeAliases.push(pascalTypeName.replace('Vec', 'Vector')); } else { - pascalTypeName = typeInfo.fnName.charAt(0).toUpperCase() - + typeInfo.fnName.slice(1); + pascalTypeName = + typeInfo.fnName.charAt(0).toUpperCase() + typeInfo.fnName.slice(1); if (pascalTypeName === 'Sampler2D') { - typeAliases.push('Texture') + typeAliases.push('Texture'); } else if (/^vec/.test(typeInfo.fnName)) { typeAliases.push(pascalTypeName.replace('Vec', 'Vector')); } } - typeMethods.push(...[pascalTypeName, ...typeAliases].flatMap((typeName) => [ - { - name: `uniform${typeName}`, - overloads: [{ - params: [ + typeMethods.push( + ...[pascalTypeName, ...typeAliases].flatMap(typeName => [ + { + name: `uniform${typeName}`, + overloads: [ { - name: 'defaultValue', - type: { type: 'NameExpression', name: 'any' }, - optional: true + params: [ + { + name: 'defaultValue', + type: { type: 'NameExpression', name: 'any' }, + optional: true + } + ], + return: { + type: { type: 'NameExpression', name: 'any' } + } } ], - return: { - type: { type: 'NameExpression', name: 'any' } - } - }], - description: `Create a ${pascalTypeName} uniform variable`, - static: false - }, - ...['varying', 'shared'].map((prefix) => ({ - name: `${prefix}${typeName}`, - overloads: [{ - params: [], - return: { - type: { type: 'NameExpression', name: 'any' } - } - }], - description: `Create a shared ${pascalTypeName} to pass data between hooks`, - static: false - })) - ])); + description: `Create a ${pascalTypeName} uniform variable`, + static: false + }, + ...['varying', 'shared'].map(prefix => ({ + name: `${prefix}${typeName}`, + overloads: [ + { + params: [], + return: { + type: { type: 'NameExpression', name: 'any' } + } + } + ], + description: `Create a shared ${pascalTypeName} to pass data between hooks`, + static: false + })) + ]) + ); } // Add type casting functions (DataType constructor functions) @@ -171,18 +185,20 @@ function processStrandsFunctions() { const typeInfo = DataType[type]; const castingMethod = { name: typeInfo.fnName, - overloads: [{ - params: [ - { - name: 'value', - type: { type: 'NameExpression', name: 'any' }, - optional: false + overloads: [ + { + params: [ + { + name: 'value', + type: { type: 'NameExpression', name: 'any' }, + optional: false + } + ], + return: { + type: { type: 'NameExpression', name: 'any' } } - ], - return: { - type: { type: 'NameExpression', name: 'any' } } - }], + ], description: `GLSL type constructor for ${typeInfo.fnName}`, static: false }; @@ -199,10 +215,17 @@ function convertTypeToTypeScript(typeNode, options = {}) { // Validate that typeNode is always an object if (typeof typeNode !== 'object' || Array.isArray(typeNode)) { - throw new Error(`convertTypeToTypeScript expects an object, got: ${typeof typeNode} - ${JSON.stringify(typeNode)}`); + throw new Error( + `convertTypeToTypeScript expects an object, got: ${typeof typeNode} - ${JSON.stringify(typeNode)}` + ); } - const { currentClass = null, isInsideNamespace = false, inGlobalMode = false, isConstantDef = false } = options; + const { + currentClass = null, + isInsideNamespace = false, + inGlobalMode = false, + isConstantDef = false + } = options; switch (typeNode.type) { case 'NameExpression': { @@ -210,19 +233,19 @@ function convertTypeToTypeScript(typeNode, options = {}) { // Handle primitive types const primitiveTypes = { - 'String': 'string', - 'Number': 'number', - 'Integer': 'number', - 'Boolean': 'boolean', - 'Void': 'void', - 'Object': 'object', - 'Any': 'any', - 'Array': 'any[]', - 'Promise': 'Promise', - 'Function': 'Function', - 'HTMLElement': 'HTMLElement', - 'Event': 'Event', - 'Request': 'Request' + String: 'string', + Number: 'number', + Integer: 'number', + Boolean: 'boolean', + Void: 'void', + Object: 'object', + Any: 'any', + Array: 'any[]', + Promise: 'Promise', + Function: 'Function', + HTMLElement: 'HTMLElement', + Event: 'Event', + Request: 'Request' }; if (primitiveTypes[typeName]) { @@ -230,7 +253,10 @@ function convertTypeToTypeScript(typeNode, options = {}) { } // Handle self-referential types within the same class - if (currentClass && (typeName === `p5.${currentClass}` || typeName === currentClass)) { + if ( + currentClass && + (typeName === `p5.${currentClass}` || typeName === currentClass) + ) { return currentClass; } @@ -279,7 +305,10 @@ function convertTypeToTypeScript(typeNode, options = {}) { const baseTypeName = typeNode.expression.name; if (baseTypeName === 'Array' && typeNode.applications.length === 1) { - const innerType = convertTypeToTypeScript(typeNode.applications[0], options); + const innerType = convertTypeToTypeScript( + typeNode.applications[0], + options + ); return `${innerType}[]`; } @@ -319,7 +348,9 @@ function convertTypeToTypeScript(typeNode, options = {}) { return 'undefined'; case 'ArrayType': { - const innerTypes = typeNode.elements.map(e => convertTypeToTypeScript(e, options)); + const innerTypes = typeNode.elements.map(e => + convertTypeToTypeScript(e, options) + ); return `[${innerTypes.join(', ')}]`; } @@ -347,7 +378,10 @@ function convertTypeToTypeScript(typeNode, options = {}) { // Check if typedef represents a real object shape function hasTypedefProperties(typedefEntry) { - if (!Array.isArray(typedefEntry.properties) || typedefEntry.properties.length === 0) { + if ( + !Array.isArray(typedefEntry.properties) || + typedefEntry.properties.length === 0 + ) { return false; } // Reject self-referential single-property typedefs @@ -393,15 +427,21 @@ function convertFunctionTypeForInterface(typeNode, options) { } // Generate a TypeScript interface from a typedef with @property fields -function generateTypedefInterface(name, typedefEntry, options = {}, indent = 2) { +function generateTypedefInterface( + name, + typedefEntry, + options = {}, + indent = 2 +) { const pad = ' '.repeat(indent); const innerPad = ' '.repeat(indent + 2); let output = ''; if (typedefEntry.description) { - const descStr = typeof typedefEntry.description === 'string' - ? typedefEntry.description - : descriptionStringForTypeScript(typedefEntry.description); + const descStr = + typeof typedefEntry.description === 'string' + ? typedefEntry.description + : descriptionStringForTypeScript(typedefEntry.description); if (descStr) { output += `${pad}/**\n`; output += formatJSDocComment(descStr, indent) + '\n'; @@ -419,9 +459,10 @@ function generateTypedefInterface(name, typedefEntry, options = {}, indent = 2) const optMark = isOptional ? '?' : ''; if (prop.description) { - const propDescStr = typeof prop.description === 'string' - ? prop.description.trim() - : descriptionStringForTypeScript(prop.description); + const propDescStr = + typeof prop.description === 'string' + ? prop.description.trim() + : descriptionStringForTypeScript(prop.description); if (propDescStr) { output += `${innerPad}/** ${propDescStr} */\n`; } @@ -435,7 +476,9 @@ function generateTypedefInterface(name, typedefEntry, options = {}, indent = 2) const retPart = sig.substring(arrowIdx + 2).trim(); output += `${innerPad}${propName}${paramsPart}: ${retPart};\n`; } else { - const tsType = rawType ? convertTypeToTypeScript(rawType, options) : 'any'; + const tsType = rawType + ? convertTypeToTypeScript(rawType, options) + : 'any'; output += `${innerPad}${propName}${optMark}: ${tsType};\n`; } } @@ -451,7 +494,7 @@ const typescriptStrategy = { return context.module === 'Foundation'; }, - processDescription: (desc) => descriptionStringForTypeScript(desc), + processDescription: desc => descriptionStringForTypeScript(desc), processType: (type, param) => { // Return an object with the original type preserved @@ -483,10 +526,7 @@ const typescriptStrategy = { const processed = processData(rawData, typescriptStrategy); function normalizeIdentifier(name) { - return ( - '0123456789'.includes(name[0]) || - name === 'class' - ) ? '$' + name : name; + return '0123456789'.includes(name[0]) || name === 'class' ? '$' + name : name; } function formatJSDocComment(text, indentLevel = 0) { @@ -498,23 +538,23 @@ function formatJSDocComment(text, indentLevel = 0) { .map(line => line.trim()) .reduce((acc, line) => { if (acc.length === 0 && line === '') return acc; - if (acc.length > 0 && line === '' && acc[acc.length - 1] === '') return acc; + if (acc.length > 0 && line === '' && acc[acc.length - 1] === '') + return acc; acc.push(line); return acc; }, []) .filter((line, i, arr) => i < arr.length - 1 || line !== ''); - return lines - .map(line => `${indent} * ${line}`) - .join('\n'); + return lines.map(line => `${indent} * ${line}`).join('\n'); } function generateObjectInterface(param, allParams, options = {}) { // Check if this is an object parameter (either required or optional) - const isObjectParam = param.type && ( - (param.type.type === 'OptionalType' && param.type.expression?.name === 'Object') || - (param.type.type === 'NameExpression' && param.type.name === 'Object') - ); + const isObjectParam = + param.type && + ((param.type.type === 'OptionalType' && + param.type.expression?.name === 'Object') || + (param.type.type === 'NameExpression' && param.type.name === 'Object')); if (!isObjectParam || !param.name) { return null; @@ -522,18 +562,18 @@ function generateObjectInterface(param, allParams, options = {}) { let nestedParams = []; - // First, check if the parameter has a properties array (JSDoc properties field) if (param.properties && Array.isArray(param.properties)) { - nestedParams = param.properties.filter(prop => - prop.name && prop.name.startsWith(param.name + '.') + nestedParams = param.properties.filter( + prop => prop.name && prop.name.startsWith(param.name + '.') ); } // Fallback: Look for nested parameters with dot notation in allParams if (nestedParams.length === 0) { - nestedParams = allParams.filter(p => - p.name && p.name.startsWith(param.name + '.') && p.name !== param.name + nestedParams = allParams.filter( + p => + p.name && p.name.startsWith(param.name + '.') && p.name !== param.name ); } @@ -544,9 +584,14 @@ function generateObjectInterface(param, allParams, options = {}) { // Generate interface properties const properties = nestedParams.map(nestedParam => { const propName = nestedParam.name.substring(param.name.length + 1); // Remove 'paramName.' prefix - const propType = nestedParam.type ? convertTypeToTypeScript(nestedParam.type, options) : 'any'; + const propType = nestedParam.type + ? convertTypeToTypeScript(nestedParam.type, options) + : 'any'; // Properties are optional if they have a default value or are explicitly marked as optional - const isOptional = nestedParam.optional || nestedParam.type?.type === 'OptionalType' || nestedParam.default !== undefined; + const isOptional = + nestedParam.optional || + nestedParam.type?.type === 'OptionalType' || + nestedParam.default !== undefined; return `${propName}${isOptional ? '?' : ''}: ${propType}`; }); @@ -594,21 +639,31 @@ function generateMethodDeclaration(method, options = {}) { if (method.overloads?.[0]?.params) { method.overloads[0].params.forEach(param => { if (param.description) { - output += formatJSDocComment(`@param ${param.name} ${param.description}`, commentIndent) + '\n'; + output += + formatJSDocComment( + `@param ${param.name} ${param.description}`, + commentIndent + ) + '\n'; } }); } // Add return docs if (method.return?.description) { - output += formatJSDocComment(`@returns ${method.return.description}`, commentIndent) + '\n'; + output += + formatJSDocComment( + `@returns ${method.return.description}`, + commentIndent + ) + '\n'; } output += `${indent} */\n`; } const staticPrefix = method.static ? 'static ' : ''; - const declarationPrefix = globalFunction ? 'function ' : `${indent}${staticPrefix}`; + const declarationPrefix = globalFunction + ? 'function ' + : `${indent}${staticPrefix}`; // Generate overload declarations if (method.overloads && method.overloads.length > 0) { @@ -618,7 +673,11 @@ function generateMethodDeclaration(method, options = {}) { .join(', '); let returnType = 'void'; - if (overload.chainable && !globalFunction && options.currentClass !== 'p5') { + if ( + overload.chainable && + !globalFunction && + options.currentClass !== 'p5' + ) { returnType = options.currentClass || 'this'; // TODO: Decide what should be chainable. Many of these are accidental / not thought through } else if (overload.return && overload.return.type) { @@ -637,7 +696,9 @@ function generateMethodDeclaration(method, options = {}) { function generateClassDeclaration(classData) { let output = ''; - const className = classData.name.startsWith('p5.') ? classData.name.substring(3) : classData.name; + const className = classData.name.startsWith('p5.') + ? classData.name.substring(3) + : classData.name; const actualClassName = className === 'Graphics' ? '__Graphics' : className; if (classData.description) { @@ -646,14 +707,22 @@ function generateClassDeclaration(classData) { output += ' */\n'; } - const extendsClause = classData.extends ? ` extends ${classData.extends}` : ''; + const extendsClause = classData.extends + ? ` extends ${classData.extends}` + : ''; output += ` class ${actualClassName}${extendsClause} {\n`; // Constructor if (classData.params?.length > 0) { output += ' constructor('; output += classData.params - .map(param => generateParamDeclaration(param, { currentClass: className, isInsideNamespace: true }, classData.params)) + .map(param => + generateParamDeclaration( + param, + { currentClass: className, isInsideNamespace: true }, + classData.params + ) + ) .join(', '); output += ');\n\n'; } @@ -662,12 +731,14 @@ function generateClassDeclaration(classData) { const originalClassName = classData.name; // Class methods - const classMethodsList = Object.values(processed.classMethods[originalClassName] || {}); + const classMethodsList = Object.values( + processed.classMethods[originalClassName] || {} + ); const methodNames = new Set(classMethodsList.map(method => method.name)); // Class properties - const classProperties = processed.classitems.filter(item => - item.class === originalClassName && item.itemtype === 'property' + const classProperties = processed.classitems.filter( + item => item.class === originalClassName && item.itemtype === 'property' ); classProperties.forEach(prop => { @@ -712,7 +783,11 @@ function generateTypeDefinitions() { // First, define all constants at the top level with their actual values const seenConstants = new Set(); const p5Constants = processed.classitems.filter(item => { - if (item.class === 'p5' && item.itemtype === 'property' && item.name in processed.consts) { + if ( + item.class === 'p5' && + item.itemtype === 'property' && + item.name in processed.consts + ) { // Skip defineProperty, undefined and avoid duplicates if (item.name === 'defineProperty' || !item.name) { return false; @@ -736,7 +811,10 @@ function generateTypeDefinitions() { output += formatJSDocComment(constant.description, 0) + '\n'; output += ' */\n'; } - const type = convertTypeToTypeScript(constant.type, { isInsideNamespace: false, isConstantDef: true }); + const type = convertTypeToTypeScript(constant.type, { + isInsideNamespace: false, + isConstantDef: true + }); const isMutable = mutableProperties.has(constant.name); const declaration = isMutable ? 'declare let' : 'declare const'; output += `${declaration} ${constant.name}: ${type};\n\n`; @@ -746,18 +824,23 @@ function generateTypeDefinitions() { // Generate main p5 class output += 'declare class p5 {\n'; - output += ' constructor(sketch?: (p: p5) => void, node?: HTMLElement, sync?: boolean);\n\n'; + output += + ' constructor(sketch?: (p: p5) => void, node?: HTMLElement, sync?: boolean);\n\n'; const p5Options = { currentClass: 'p5', isInsideNamespace: false }; // Generate p5 static methods - const p5StaticMethods = Object.values(processed.classMethods.p5 || {}).filter(method => method.static); + const p5StaticMethods = Object.values(processed.classMethods.p5 || {}).filter( + method => method.static + ); p5StaticMethods.forEach(method => { output += generateMethodDeclaration(method, p5Options); }); // Generate p5 instance methods - const p5InstanceMethods = Object.values(processed.classMethods.p5 || {}).filter(method => !method.static); + const p5InstanceMethods = Object.values( + processed.classMethods.p5 || {} + ).filter(method => !method.static); p5InstanceMethods.forEach(method => { output += generateMethodDeclaration(method, p5Options); }); @@ -795,7 +878,12 @@ function generateTypeDefinitions() { const namespaceOptions = { isInsideNamespace: true }; for (const [name, typedefEntry] of Object.entries(typedefs)) { if (hasTypedefProperties(typedefEntry)) { - output += generateTypedefInterface(name, typedefEntry, namespaceOptions, 2); + output += generateTypedefInterface( + name, + typedefEntry, + namespaceOptions, + 2 + ); } } @@ -808,7 +896,14 @@ function generateTypeDefinitions() { // Generate placeholder types for private classes that we need to be able to // reference, but have no public APIs - const privateClasses = ['Renderer', 'Renderer2D', 'RendererGL', 'FramebufferTexture', 'Texture', 'Quat']; + const privateClasses = [ + 'Renderer', + 'Renderer2D', + 'RendererGL', + 'FramebufferTexture', + 'Texture', + 'Quat' + ]; // Define base classes for private classes, if they should extend something const privateClassBases = { Renderer: 'Element' }; for (const className of privateClasses) { @@ -847,17 +942,26 @@ p5: P5; globalDefinitions += `${constant.name}: typeof P5.${constant.name};\n\n`; }); - const globalP5Methods = Object.values(processed.classMethods.p5 || {}) - .filter(method => !method.static && method.name !== 'p5'); + const globalP5Methods = Object.values(processed.classMethods.p5 || {}).filter( + method => !method.static && method.name !== 'p5' + ); globalP5Methods.forEach(method => { - globalDefinitions += generateMethodDeclaration(method, { currentClass: 'p5', isInsideNamespace: true, inGlobalMode: true }); + globalDefinitions += generateMethodDeclaration(method, { + currentClass: 'p5', + isInsideNamespace: true, + inGlobalMode: true + }); }); // Add strands functions to global scope const conflictingDOMFunctions = ['length']; // Add other conflicting function names here as needed strandsMethods.forEach(method => { if (!conflictingDOMFunctions.includes(method.name)) { - globalDefinitions += generateMethodDeclaration(method, { currentClass: 'p5', isInsideNamespace: true, inGlobalMode: true }); + globalDefinitions += generateMethodDeclaration(method, { + currentClass: 'p5', + isInsideNamespace: true, + inGlobalMode: true + }); } }); @@ -876,17 +980,27 @@ p5: P5; globalDefinitions += '\n'; // Mirror typedef interfaces for global-mode usage - const globalNamespaceOptions = { isInsideNamespace: true, inGlobalMode: true }; + const globalNamespaceOptions = { + isInsideNamespace: true, + inGlobalMode: true + }; for (const [name, typedefEntry] of Object.entries(typedefs)) { if (hasTypedefProperties(typedefEntry)) { - globalDefinitions += generateTypedefInterface(name, typedefEntry, globalNamespaceOptions, 2); + globalDefinitions += generateTypedefInterface( + name, + typedefEntry, + globalNamespaceOptions, + 2 + ); } } // Add all real classes as both types and constructors Object.values(processed.classes).forEach(classData => { if (classData.name !== 'p5') { - const className = classData.name.startsWith('p5.') ? classData.name.substring(3) : classData.name; + const className = classData.name.startsWith('p5.') + ? classData.name.substring(3) + : classData.name; // For Graphics, use __Graphics for constructor if (className === 'Graphics') { globalDefinitions += ` type ${className} = P5.${className};\n`; @@ -927,13 +1041,23 @@ p5: P5; // Also declare functions in global scope globalP5Methods.forEach(method => { - globalDefinitions += generateMethodDeclaration(method, { currentClass: 'p5', isInsideNamespace: true, inGlobalMode: true, globalFunction: true }); + globalDefinitions += generateMethodDeclaration(method, { + currentClass: 'p5', + isInsideNamespace: true, + inGlobalMode: true, + globalFunction: true + }); }); // Add strands functions as global functions strandsMethods.forEach(method => { if (!conflictingDOMFunctions.includes(method.name)) { - globalDefinitions += generateMethodDeclaration(method, { currentClass: 'p5', isInsideNamespace: true, inGlobalMode: true, globalFunction: true }); + globalDefinitions += generateMethodDeclaration(method, { + currentClass: 'p5', + isInsideNamespace: true, + inGlobalMode: true, + globalFunction: true + }); } }); @@ -947,10 +1071,13 @@ p5: P5; const { instanceDefinitions, globalDefinitions } = generateTypeDefinitions(); fs.writeFileSync(path.join(__dirname, '../types/p5.d.ts'), instanceDefinitions); -fs.writeFileSync(path.join(__dirname, '../types/global.d.ts'), globalDefinitions); +fs.writeFileSync( + path.join(__dirname, '../types/global.d.ts'), + globalDefinitions +); console.log('TypeScript definitions generated successfully!'); // Apply patches console.log('Applying TypeScript patches...'); -applyPatches(); \ No newline at end of file +applyPatches(); diff --git a/visual-report.js b/visual-report.js index 3a29e223cb..0f579dd676 100644 --- a/visual-report.js +++ b/visual-report.js @@ -4,15 +4,21 @@ const SLASH_REGEX = /\//g; async function generateVisualReport() { const expectedDir = path.join(process.cwd(), 'test/unit/visual/screenshots'); - const actualDir = path.join(process.cwd(), 'test/unit/visual/actual-screenshots'); - const outputFile = path.join(process.cwd(), 'test/unit/visual/visual-report.html'); - + const actualDir = path.join( + process.cwd(), + 'test/unit/visual/actual-screenshots' + ); + const outputFile = path.join( + process.cwd(), + 'test/unit/visual/visual-report.html' + ); + // Make sure the output directory exists const outputDir = path.dirname(outputFile); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } - + // Function to read image file and convert to data URL function imageToDataURL(filePath) { try { @@ -24,7 +30,7 @@ async function generateVisualReport() { return null; } } - + // Create a lookup map for actual screenshots function createActualScreenshotMap() { const actualMap = new Map(); @@ -32,33 +38,35 @@ async function generateVisualReport() { console.warn(`Actual screenshots directory does not exist: ${actualDir}`); return actualMap; } - + const files = fs.readdirSync(actualDir); for (const file of files) { if (file.endsWith('.png') && !file.endsWith('-diff.png')) { actualMap.set(file, path.join(actualDir, file)); } } - + return actualMap; } - + const actualScreenshotMap = createActualScreenshotMap(); - + // Recursively find all test cases function findTestCases(dir, prefix = '') { const testCases = []; - + if (!fs.existsSync(path.join(dir, prefix))) { console.warn(`Directory does not exist: ${path.join(dir, prefix)}`); return testCases; } - - const entries = fs.readdirSync(path.join(dir, prefix), { withFileTypes: true }); - + + const entries = fs.readdirSync(path.join(dir, prefix), { + withFileTypes: true + }); + for (const entry of entries) { const fullPath = path.join(prefix, entry.name); - + if (entry.isDirectory()) { // Recursively search subdirectories testCases.push(...findTestCases(dir, fullPath)); @@ -66,42 +74,43 @@ async function generateVisualReport() { // Found a test case const metadataPath = path.join(dir, fullPath); let metadata; - + try { metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8')); } catch (error) { console.error(`Failed to read metadata: ${metadataPath}`, error); continue; } - + const testDir = path.dirname(fullPath); - + const test = { name: testDir, numScreenshots: metadata.numScreenshots || 0, screenshots: [] }; - + // Create flattened name for lookup const flattenedName = testDir.replace(SLASH_REGEX, '-'); - + // Collect all screenshots for this test for (let i = 0; i < test.numScreenshots; i++) { const screenshotName = i.toString().padStart(3, '0') + '.png'; const expectedPath = path.join(dir, testDir, screenshotName); - + // Use flattened name for actual screenshots const actualScreenshotName = `${flattenedName}-${i.toString().padStart(3, '0')}.png`; - const actualPath = actualScreenshotMap.get(actualScreenshotName) || null; - + const actualPath = + actualScreenshotMap.get(actualScreenshotName) || null; + // Use flattened name for diff image const diffScreenshotName = `${flattenedName}-${i.toString().padStart(3, '0')}-diff.png`; const diffPath = path.join(actualDir, diffScreenshotName); - + const hasExpected = fs.existsSync(expectedPath); const hasActual = actualPath && fs.existsSync(actualPath); const hasDiff = fs.existsSync(diffPath); - + const screenshot = { index: i, expectedImage: hasExpected ? imageToDataURL(expectedPath) : null, @@ -109,41 +118,43 @@ async function generateVisualReport() { diffImage: hasDiff ? imageToDataURL(diffPath) : null, passed: hasExpected && hasActual && !hasDiff }; - + test.screenshots.push(screenshot); } - + // Don't add tests with no screenshots if (test.screenshots.length > 0) { testCases.push(test); } } } - + return testCases; } - + // Find all test cases from the expected directory const testCases = findTestCases(expectedDir); - + if (testCases.length === 0) { - console.warn('No test cases found. Check if the expected directory is correct.'); + console.warn( + 'No test cases found. Check if the expected directory is correct.' + ); } - + // Count passed/failed tests and screenshots const totalTests = testCases.length; let passedTests = 0; let totalScreenshots = 0; let passedScreenshots = 0; - + for (const test of testCases) { const testPassed = test.screenshots.every(screenshot => screenshot.passed); if (testPassed) passedTests++; - + totalScreenshots += test.screenshots.length; passedScreenshots += test.screenshots.filter(s => s.passed).length; } - + // Generate HTML const html = ` @@ -311,25 +322,28 @@ async function generateVisualReport() {

    Summary

    Total Tests: ${totalTests}
    - Passed Tests: ${passedTests} (${totalTests > 0 ? Math.round(passedTests/totalTests*100) : 0}%)
    - Failed Tests: ${totalTests - passedTests} (${totalTests > 0 ? Math.round((totalTests-passedTests)/totalTests*100) : 0}%)
    + Passed Tests: ${passedTests} (${totalTests > 0 ? Math.round((passedTests / totalTests) * 100) : 0}%)
    + Failed Tests: ${totalTests - passedTests} (${totalTests > 0 ? Math.round(((totalTests - passedTests) / totalTests) * 100) : 0}%)
    Total Screenshots: ${totalScreenshots}
    - Passed Screenshots: ${passedScreenshots} (${totalScreenshots > 0 ? Math.round(passedScreenshots/totalScreenshots*100) : 0}%)
    + Passed Screenshots: ${passedScreenshots} (${totalScreenshots > 0 ? Math.round((passedScreenshots / totalScreenshots) * 100) : 0}%)
    Report Generated: ${new Date().toLocaleString()}

    ${node.value.replace(//g, '>')}
    - ${testCases.map(test => { - const passed = test.screenshots.every(s => s.passed); - return ` + ${testCases + .map(test => { + const passed = test.screenshots.every(s => s.passed); + return `
    ${test.name} ${passed ? 'PASS' : 'FAIL'}
    - ${test.screenshots.map(screenshot => ` + ${test.screenshots + .map( + screenshot => `
    Screenshot #${screenshot.index + 1} @@ -340,29 +354,40 @@ async function generateVisualReport() {
    Expected
    - ${screenshot.expectedImage ? - `Expected Result` : - `
    No expected image found
    `} + ${ + screenshot.expectedImage + ? `Expected Result` + : `
    No expected image found
    ` + }
    Actual
    - ${screenshot.actualImage ? - `Actual Result` : - `
    No actual image found
    `} + ${ + screenshot.actualImage + ? `Actual Result` + : `
    No actual image found
    ` + }
    - ${screenshot.diffImage ? ` + ${ + screenshot.diffImage + ? `
    Diff
    Difference
    - ` : ''} + ` + : '' + }
    - `).join('')} + ` + ) + .join('')}
    `; - }).join('')} + }) + .join('')}