diff --git a/.gitignore b/.gitignore index c6bba59..e3878b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,130 +1,9 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories +deploy/ node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test +.editor +.vscode +dist/ +cache/ +*.sfk -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* +*.blend1 diff --git a/README.md b/README.md index 4796fbb..b34901b 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,15 @@ The Wonderland Engine YouTube Channel ([@WonderlandEngine](https://www.youtube.c Each folder in this repository corresponds to a specific project or tutorial featured on the channel. Inside, you'll find all the necessary code, assets, and instructions to help you get started with recreating or extending the projects on your own. ### Projects Overview -- **[Project Name 1]**: Brief description of Project 1. -- **[Project Name 2]**: Brief description of Project 2. -- *(More projects will be listed here as they are added to the channel and repository.)* + +#### */WebAPI-Tutorial* +Title: +Use any Web API in Wonderland Engine - Tutorial +Description: +Short tutorial on how to integrate the Sketchfab API into a Wonderland Engine WebXR app. +Video: [YouTube](https://www.youtube.com/watch?v=vecuXqagouA) + +*(More projects will be listed here as they are added to the channel and repository.)* ## How to Use To get started with a project: diff --git a/WebAPI-Tutorial/README.md b/WebAPI-Tutorial/README.md new file mode 100644 index 0000000..9b14aaa --- /dev/null +++ b/WebAPI-Tutorial/README.md @@ -0,0 +1,5 @@ +# Web API Tutorial + +![Video Cover](Screenshot.png) + +[Web API Tutorial](https://www.youtube.com/watch?v=vecuXqagouA). diff --git a/WebAPI-Tutorial/Screenshot.png b/WebAPI-Tutorial/Screenshot.png new file mode 100644 index 0000000..e995200 Binary files /dev/null and b/WebAPI-Tutorial/Screenshot.png differ diff --git a/WebAPI-Tutorial/WebAPI-Tutorial.wlp b/WebAPI-Tutorial/WebAPI-Tutorial.wlp new file mode 100644 index 0000000..179e6ad --- /dev/null +++ b/WebAPI-Tutorial/WebAPI-Tutorial.wlp @@ -0,0 +1,2840 @@ +{ + "objects": { + "14": { + "name": "Player", + "translation": [ + -2.5, + 0.0, + 1.1575535 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scaling": [ + 0.9999998, + 1.0, + 0.9999998 + ] + }, + "15": { + "name": "FloorPlane", + "components": [ + { + "type": "mesh", + "mesh": { + "mesh": "7", + "material": "20" + }, + "active": true + }, + { + "type": "collision", + "collision": { + "collider": "aabb", + "aabb": { + "extents": [ + 4.0, + 0.05, + 4.0 + ] + }, + "groups": 4 + } + } + ], + "translation": [ + 0.0, + 0.0, + 1.1920928955078125e-7 + ], + "rotation": [ + -0.7072496, + 0.0, + 0.0, + 0.7069638 + ], + "scaling": [ + 4.0, + 4.0, + 4.0 + ] + }, + "16": { + "name": "Cube", + "components": [ + { + "type": "mesh", + "mesh": { + "mesh": "8", + "material": "22" + } + } + ], + "translation": [ + 2.9802322387695313e-8, + 0.4999999, + 5.960464477539065e-8 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scaling": [ + 0.5, + 0.5, + 0.5 + ] + }, + "17": { + "name": "NonVrCamera", + "parent": "205", + "components": [ + { + "type": "view" + }, + { + "type": "mouse-look" + }, + { + "type": "cursor", + "cursor": { + "collisionGroup": 0, + "handedness": "none" + } + }, + { + "type": "howler-audio-listener" + }, + { + "type": "vr-mode-active-switch", + "vr-mode-active-switch": { + "activateComponents": "in non-VR" + } + } + ], + "translation": [ + 0.0, + 0.0, + 0.0 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scaling": [ + 1.0, + 1.0, + 1.0 + ] + }, + "18": { + "name": "EyeLeft", + "parent": "205", + "components": [ + { + "type": "view" + }, + { + "type": "input", + "input": { + "type": "eye left" + } + } + ], + "translation": [ + 0.0, + 0.0, + -2.384185791015625e-7 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scaling": [ + 1.0, + 1.0, + 1.0 + ] + }, + "19": { + "name": "EyeRight", + "parent": "205", + "components": [ + { + "type": "view" + }, + { + "type": "input", + "input": { + "type": "eye right" + } + } + ], + "translation": [ + 0.0, + 0.0, + -2.384185791015625e-7 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scaling": [ + 1.0, + 1.0, + 1.0 + ] + }, + "21": { + "name": "Light", + "components": [ + { + "type": "light", + "light": { + "intensity": 3.3329999 + } + } + ], + "translation": [ + 0.0, + 3.0, + 2.0 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scaling": [ + 1.0, + 1.0, + 1.0 + ] + }, + "23": { + "name": "Light.000", + "components": [ + { + "type": "light", + "light": { + "intensity": 3.3329999 + } + } + ], + "translation": [ + -1.0, + 3.0, + -1.0 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scaling": [ + 1.0, + 1.0, + 1.0 + ] + }, + "24": { + "name": "Cone", + "components": [ + { + "type": "mesh", + "mesh": { + "mesh": "10", + "material": "25" + } + } + ], + "translation": [ + 1.5, + 1.0, + -1.4901161193847656e-8 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scaling": [ + 1.0, + 1.0, + 1.0 + ] + }, + "27": { + "name": "Sphere", + "components": [ + { + "type": "mesh", + "mesh": { + "mesh": "9", + "material": "26" + } + } + ], + "translation": [ + 0.4999999, + 0.75, + -1.25 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scaling": [ + 0.75, + 0.75, + 0.75 + ] + }, + "53": { + "link": { + "name": "root", + "file": "models\\oculus-touch-v2-left.glb" + }, + "parent": "130", + "name": "ControllerModelLeft", + "translation": [ + -0.0117514, + 0.0008869, + 0.0161192 + ], + "rotation": [ + -0.0, + 0.7316615, + 0.6816681, + 3.196286257889369e-8 + ], + "scaling": [ + 0.9999998, + 0.9999999, + 0.9999997 + ] + }, + "54": { + "link": { + "name": "controller {})` on the `cursor-target` component used + * with the button to define the button's action. + * + * Supports interaction with `finger-cursor` component for hand tracking. + */ +export class ButtonComponent extends Component { + static TypeName = 'button'; + static Properties = { + /** Object that has the button's mesh attached */ + buttonMeshObject: Property.object(), + /** Material to apply when the user hovers the button */ + hoverMaterial: Property.material(), + }; + + static onRegister(engine) { + engine.registerComponent(HowlerAudioSource); + engine.registerComponent(CursorTarget); + } + + /* Position to return to when "unpressing" the button */ + returnPos = new Float32Array(3); + + start() { + this.mesh = this.buttonMeshObject.getComponent(MeshComponent); + this.defaultMaterial = this.mesh.material; + this.buttonMeshObject.getTranslationLocal(this.returnPos); + + this.target = + this.object.getComponent(CursorTarget) || + this.object.addComponent(CursorTarget); + + this.soundClick = this.object.addComponent(HowlerAudioSource, { + src: 'sfx/click.wav', + spatial: true, + }); + this.soundUnClick = this.object.addComponent(HowlerAudioSource, { + src: 'sfx/unclick.wav', + spatial: true, + }); + } + + onActivate() { + this.target.onHover.add(this.onHover); + this.target.onUnhover.add(this.onUnhover); + this.target.onDown.add(this.onDown); + this.target.onUp.add(this.onUp); + } + + onDeactivate() { + this.target.onHover.remove(this.onHover); + this.target.onUnhover.remove(this.onUnhover); + this.target.onDown.remove(this.onDown); + this.target.onUp.remove(this.onUp); + } + + /* Called by 'cursor-target' */ + onHover = (_, cursor) => { + this.mesh.material = this.hoverMaterial; + if (cursor.type === 'finger-cursor') { + this.onDown(_, cursor); + } + + hapticFeedback(cursor.object, 0.5, 50); + } + + /* Called by 'cursor-target' */ + onDown = (_, cursor) => { + this.soundClick.play(); + this.buttonMeshObject.translate([0.0, -0.1, 0.0]); + hapticFeedback(cursor.object, 1.0, 20); + } + + /* Called by 'cursor-target' */ + onUp = (_, cursor) => { + this.soundUnClick.play(); + this.buttonMeshObject.setTranslationLocal(this.returnPos); + hapticFeedback(cursor.object, 0.7, 20); + } + + /* Called by 'cursor-target' */ + onUnhover = (_, cursor) => { + this.mesh.material = this.defaultMaterial; + if (cursor.type === 'finger-cursor') { + this.onUp(_, cursor); + } + + hapticFeedback(cursor.object, 0.3, 50); + } +} diff --git a/WebAPI-Tutorial/js/index.js b/WebAPI-Tutorial/js/index.js new file mode 100644 index 0000000..c5d630f --- /dev/null +++ b/WebAPI-Tutorial/js/index.js @@ -0,0 +1,108 @@ +/** + * /!\ This file is auto-generated. + * + * This is the entry point of your standalone application. + * + * There are multiple tags used by the editor to inject code automatically: + * - `wle:auto-imports:start` and `wle:auto-imports:end`: The list of import statements + * - `wle:auto-register:start` and `wle:auto-register:end`: The list of component to register + * - `wle:auto-constants:start` and `wle:auto-constants:end`: The project's constants, + * such as the project's name, whether it should use the physx runtime, etc... + * - `wle:auto-benchmark:start` and `wle:auto-benchmark:end`: Append the benchmarking code + */ + +/* wle:auto-imports:start */ +import {Cursor} from '@wonderlandengine/components'; +import {CursorTarget} from '@wonderlandengine/components'; +import {FingerCursor} from '@wonderlandengine/components'; +import {HandTracking} from '@wonderlandengine/components'; +import {HowlerAudioListener} from '@wonderlandengine/components'; +import {MouseLookComponent} from '@wonderlandengine/components'; +import {PlayerHeight} from '@wonderlandengine/components'; +import {TeleportComponent} from '@wonderlandengine/components'; +import {VrModeActiveSwitch} from '@wonderlandengine/components'; +import {ButtonComponent} from './button.js'; +import {SketchfabSearch} from './sketchfab-search.js'; +/* wle:auto-imports:end */ + +import {loadRuntime} from '@wonderlandengine/api'; +import * as API from '@wonderlandengine/api'; // Deprecated: Backward compatibility. + +/* wle:auto-constants:start */ +const Constants = { + ProjectName: 'WebAPI-Tutorial', + RuntimeBaseName: 'WonderlandRuntime', + WebXRRequiredFeatures: ['local',], + WebXROptionalFeatures: ['local','local-floor','hand-tracking','hit-test',], +}; +const RuntimeOptions = { + physx: false, + loader: false, + xrFramebufferScaleFactor: 1, + xrOfferSession: { + mode: 'auto', + features: Constants.WebXRRequiredFeatures, + optionalFeatures: Constants.WebXROptionalFeatures, + }, + canvas: 'canvas', +}; +/* wle:auto-constants:end */ + +const engine = await loadRuntime(Constants.RuntimeBaseName, RuntimeOptions); +Object.assign(engine, API); // Deprecated: Backward compatibility. +window.WL = engine; // Deprecated: Backward compatibility. + +engine.onSceneLoaded.once(() => { + const el = document.getElementById('version'); + if (el) setTimeout(() => el.remove(), 2000); +}); + +/* WebXR setup. */ + +function requestSession(mode) { + engine + .requestXRSession(mode, Constants.WebXRRequiredFeatures, Constants.WebXROptionalFeatures) + .catch((e) => console.error(e)); +} + +function setupButtonsXR() { + /* Setup AR / VR buttons */ + const arButton = document.getElementById('ar-button'); + if (arButton) { + arButton.dataset.supported = engine.arSupported; + arButton.addEventListener('click', () => requestSession('immersive-ar')); + } + const vrButton = document.getElementById('vr-button'); + if (vrButton) { + vrButton.dataset.supported = engine.vrSupported; + vrButton.addEventListener('click', () => requestSession('immersive-vr')); + } +} + +if (document.readyState === 'loading') { + window.addEventListener('load', setupButtonsXR); +} else { + setupButtonsXR(); +} + +/* wle:auto-register:start */ +engine.registerComponent(Cursor); +engine.registerComponent(CursorTarget); +engine.registerComponent(FingerCursor); +engine.registerComponent(HandTracking); +engine.registerComponent(HowlerAudioListener); +engine.registerComponent(MouseLookComponent); +engine.registerComponent(PlayerHeight); +engine.registerComponent(TeleportComponent); +engine.registerComponent(VrModeActiveSwitch); +engine.registerComponent(ButtonComponent); +engine.registerComponent(SketchfabSearch); +/* wle:auto-register:end */ + +engine.scene.load(`${Constants.ProjectName}.bin`).catch((e) => { + console.error(e); + window.alert(`Failed to load ${Constants.ProjectName}.bin:`, e); +}); + +/* wle:auto-benchmark:start */ +/* wle:auto-benchmark:end */ diff --git a/WebAPI-Tutorial/js/sketchfab-search.js b/WebAPI-Tutorial/js/sketchfab-search.js new file mode 100644 index 0000000..97ced96 --- /dev/null +++ b/WebAPI-Tutorial/js/sketchfab-search.js @@ -0,0 +1,64 @@ +import { Component, MeshComponent, Property, TextComponent, Texture, TextureManager } from '@wonderlandengine/api'; + +/** + * sketchfab-search + */ +export class SketchfabSearch extends Component { + static TypeName = 'sketchfab-search'; + + /* Properties that are configurable in the editor */ + static Properties = { + columnCount: Property.int(3), + searchText: Property.string('tree'), + textMaterial: Property.material(), + planeMesh: Property.mesh(), + planeMaterial: Property.material() + }; + + start() { + console.log('Starting search with parameter ', this.searchText); + + fetch('https://api.sketchfab.com/v3/search?type=models&q=' + this.searchText) + .then(response => response.json()) + .then(r => { + let i = 0; + + for (const model of r.results) { + const name = model.name; + + let row = Math.floor(i / this.columnCount); + let col = i % this.columnCount; + let y = -0.6 * row; + let x = 0.6 * col; + + const textObject = this.engine.scene.addObject(this.object); + textObject.translateLocal([x, y, 0]); + textObject.setScalingLocal([0.5, 0.5, 0.5]); + textObject.addComponent(TextComponent, { + text: name, + material: this.textMaterial + }) + + const plane = this.engine.scene.addObject(textObject); + // Clone the material so we do not change the original + const mat = this.planeMaterial.clone(); + const thumbnails = model.thumbnails.images; + const index = thumbnails.findIndex(t => t.width <= 256); + this.engine.textures.load(thumbnails[index].url, "no-cors") + .then(texture => { + mat.flatTexture = texture; + }); + + plane.setScalingLocal([0.5, 0.5, 0.5]); + plane.translateLocal([0.5, -0.5, 0]); + plane.addComponent(MeshComponent, { + mesh: this.planeMesh, + material: mat + }); + + i++; + } + }) + } + +} diff --git a/WebAPI-Tutorial/models/button.glb b/WebAPI-Tutorial/models/button.glb new file mode 100644 index 0000000..f032e58 Binary files /dev/null and b/WebAPI-Tutorial/models/button.glb differ diff --git a/WebAPI-Tutorial/models/hand-left.glb b/WebAPI-Tutorial/models/hand-left.glb new file mode 100644 index 0000000..2159ffc Binary files /dev/null and b/WebAPI-Tutorial/models/hand-left.glb differ diff --git a/WebAPI-Tutorial/models/hand-right.glb b/WebAPI-Tutorial/models/hand-right.glb new file mode 100644 index 0000000..d4adfb2 Binary files /dev/null and b/WebAPI-Tutorial/models/hand-right.glb differ diff --git a/WebAPI-Tutorial/models/oculus-touch-v2-left.glb b/WebAPI-Tutorial/models/oculus-touch-v2-left.glb new file mode 100644 index 0000000..5a1107f Binary files /dev/null and b/WebAPI-Tutorial/models/oculus-touch-v2-left.glb differ diff --git a/WebAPI-Tutorial/models/oculus-touch-v2-right.glb b/WebAPI-Tutorial/models/oculus-touch-v2-right.glb new file mode 100644 index 0000000..ca07031 Binary files /dev/null and b/WebAPI-Tutorial/models/oculus-touch-v2-right.glb differ diff --git a/WebAPI-Tutorial/models/teleport-indicator.blend b/WebAPI-Tutorial/models/teleport-indicator.blend new file mode 100644 index 0000000..ded706d Binary files /dev/null and b/WebAPI-Tutorial/models/teleport-indicator.blend differ diff --git a/WebAPI-Tutorial/models/teleport-indicator.glb b/WebAPI-Tutorial/models/teleport-indicator.glb new file mode 100644 index 0000000..14a499d Binary files /dev/null and b/WebAPI-Tutorial/models/teleport-indicator.glb differ diff --git a/WebAPI-Tutorial/package.json b/WebAPI-Tutorial/package.json new file mode 100644 index 0000000..6cfd459 --- /dev/null +++ b/WebAPI-Tutorial/package.json @@ -0,0 +1,19 @@ +{ + "name": "WebAPI-Tutorial", + "version": "1.0.0", + "description": "My Wonderland project", + "main": "js/index.js", + "type": "module", + "module": "js/index.js", + "scripts": { + "build": "echo \"The 'build' script is run by the editor and should produce your application bundle\"" + }, + "keywords": [ + "wonderland-engine" + ], + "dependencies": { + "@wonderlandengine/api": "^1.1.5", + "@wonderlandengine/components": "^1.0.7", + "gl-matrix": "^3.4.3" + } +} diff --git a/WebAPI-Tutorial/static/sfx/click.wav b/WebAPI-Tutorial/static/sfx/click.wav new file mode 100644 index 0000000..e21139d Binary files /dev/null and b/WebAPI-Tutorial/static/sfx/click.wav differ diff --git a/WebAPI-Tutorial/static/sfx/unclick.wav b/WebAPI-Tutorial/static/sfx/unclick.wav new file mode 100644 index 0000000..65f89cf Binary files /dev/null and b/WebAPI-Tutorial/static/sfx/unclick.wav differ