452 lines
11 KiB
JavaScript
452 lines
11 KiB
JavaScript
// xfg:title Keyboard
|
|
// xfg:category practice
|
|
// xfg:group interactive
|
|
|
|
/*
|
|
|
|
This example is an advanced demo.
|
|
|
|
For a better first step into this library, you should :
|
|
- check the tutorial at https://github.com/felixmariotto/three-mesh-ui/wiki/Getting-started
|
|
- consult more simple examples at https://three-mesh-ui.herokuapp.com/#basic_setup
|
|
|
|
*/
|
|
|
|
import * as THREE from 'three';
|
|
import { VRButton } from 'three/examples/jsm/webxr/VRButton.js';
|
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
import { BoxLineGeometry } from 'three/examples/jsm/geometries/BoxLineGeometry.js';
|
|
|
|
import ThreeMeshUI from 'three-mesh-ui';
|
|
import ShadowedLight from './utils/ShadowedLight.js';
|
|
|
|
import FontJSON from 'three-mesh-ui/examples/assets/fonts/msdf/roboto/regular.json';
|
|
import FontImage from 'three-mesh-ui/examples/assets/fonts/msdf/roboto/regular.png';
|
|
|
|
import Backspace from 'three-mesh-ui/examples/assets/backspace.png';
|
|
import Enter from 'three-mesh-ui/examples/assets/enter.png';
|
|
import Shift from 'three-mesh-ui/examples/assets/shift.png';
|
|
import Stats from 'three/examples/jsm/libs/stats.module';
|
|
import InteractiveRaycaster from 'three-mesh-ui/examples/interactive/InteractiveRaycaster';
|
|
import InteractiveCursor from 'three-mesh-ui/examples/interactive/listeners/InteractiveCursor';
|
|
import KeyboardElement from 'three-mesh-ui/examples/elements/keyboard/KeyboardElement';
|
|
import QwertyEnglish from 'three-mesh-ui/examples/elements/keyboard/keyboard-layouts/QwertyEnglish';
|
|
import AzertyFrench from 'three-mesh-ui/examples/elements/keyboard/keyboard-layouts/AzertyFrench';
|
|
import QwertzDeutsch from 'three-mesh-ui/examples/elements/keyboard/keyboard-layouts/QwertzDeutsch';
|
|
import QwertyGreek from 'three-mesh-ui/examples/elements/keyboard/keyboard-layouts/QwertyGreek';
|
|
import QwertyNordic from 'three-mesh-ui/examples/elements/keyboard/keyboard-layouts/QwertyNordic';
|
|
import QwertyRussian from 'three-mesh-ui/examples/elements/keyboard/keyboard-layouts/QwertyRussian';
|
|
import QwertySpanish from 'three-mesh-ui/examples/elements/keyboard/keyboard-layouts/QwertySpanish';
|
|
import VRControl from 'three-mesh-ui/examples/controls/VRControl';
|
|
import PhysicalKeyboardFeederBehavior from 'three-mesh-ui/examples/behaviors/input/PhysicalKeyboardFeederBehavior';
|
|
import KeyboardsSynchronizerBehavior from 'three-mesh-ui/examples/behaviors/input/KeyboardsSynchronizerBehavior';
|
|
import InteractiveTooltip from 'three-mesh-ui/examples/interactive/listeners/InteractiveTooltip';
|
|
import SimpleStateBehavior from 'three-mesh-ui/examples/behaviors/states/SimpleStateBehavior';
|
|
|
|
let scene,
|
|
camera,
|
|
renderer,
|
|
controls,
|
|
vrControl,
|
|
keyboard,
|
|
userText,
|
|
intersectionRoom,
|
|
layoutOptions,
|
|
interactiveRaycaster,
|
|
interactiveCursor,
|
|
stats,
|
|
keyboardSync;
|
|
|
|
// Colors
|
|
|
|
const colors = {
|
|
keyboardBack: 0x858585,
|
|
panelBack: 0x262626,
|
|
button: 0x363636,
|
|
hovered: 0x1c1c1c,
|
|
selected: 0x109c5d
|
|
};
|
|
|
|
//
|
|
|
|
window.addEventListener( 'load', init );
|
|
window.addEventListener( 'resize', onWindowResize );
|
|
|
|
//////////////////
|
|
// THREE.JS INIT
|
|
//////////////////
|
|
|
|
function init() {
|
|
|
|
scene = new THREE.Scene();
|
|
scene.background = new THREE.Color( 0x505050 );
|
|
|
|
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
|
|
|
|
renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
renderer.setPixelRatio( window.devicePixelRatio );
|
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
renderer.outputEncoding = THREE.sRGBEncoding;
|
|
renderer.xr.enabled = true;
|
|
document.body.appendChild( VRButton.createButton( renderer ) );
|
|
document.body.appendChild( renderer.domElement );
|
|
renderer.shadowMap.enabled = true;
|
|
|
|
// STATS
|
|
|
|
|
|
stats = new Stats();
|
|
stats.dom.style.left = 'auto';
|
|
stats.dom.style.right = '0px';
|
|
document.body.appendChild( stats.dom );
|
|
|
|
|
|
// LIGHT
|
|
|
|
const light = ShadowedLight( {
|
|
z: 10,
|
|
width: 6,
|
|
bias: -0.0001
|
|
} );
|
|
|
|
const hemLight = new THREE.HemisphereLight( 0x808080, 0x606060 );
|
|
|
|
scene.add( light, hemLight );
|
|
|
|
// CONTROLLERS
|
|
|
|
controls = new OrbitControls( camera, renderer.domElement );
|
|
camera.position.set( 0, 1.6, 0 );
|
|
controls.target = new THREE.Vector3( 0, 1.2, -1 );
|
|
controls.update();
|
|
|
|
//
|
|
|
|
vrControl = new VRControl( renderer );
|
|
scene.add( vrControl.controllerGrips[ 0 ], vrControl.controllers[ 0 ] );
|
|
|
|
interactiveRaycaster = new InteractiveRaycaster( camera, scene, renderer, vrControl );
|
|
|
|
interactiveCursor = new InteractiveCursor( renderer.domElement );
|
|
interactiveRaycaster.addListener( interactiveCursor );
|
|
|
|
const interactiveTooltip = new InteractiveTooltip( renderer.domElement );
|
|
interactiveRaycaster.addListener( interactiveTooltip );
|
|
|
|
interactiveRaycaster.start();
|
|
|
|
// ROOM
|
|
|
|
const room = new THREE.LineSegments(
|
|
new BoxLineGeometry( 6, 6, 6, 10, 10, 10 ).translate( 0, 3, 0 ),
|
|
new THREE.LineBasicMaterial( { color: 0x808080 } )
|
|
);
|
|
|
|
intersectionRoom = new THREE.Mesh(
|
|
new THREE.BoxGeometry( 6, 6, 6, 10, 10, 10 ).translate( 0, 3, 0 ),
|
|
new THREE.MeshBasicMaterial( {
|
|
side : THREE.BackSide,
|
|
transparent: true,
|
|
opacity: 0
|
|
} )
|
|
);
|
|
|
|
scene.add( room, intersectionRoom );
|
|
|
|
// USER INTERFACE
|
|
|
|
makeUI();
|
|
|
|
// LOOP
|
|
|
|
renderer.setAnimationLoop( loop );
|
|
|
|
}
|
|
|
|
//
|
|
|
|
function makeUI() {
|
|
|
|
const container = new THREE.Group();
|
|
container.position.set( 0, 1.4, -1.2 );
|
|
container.rotation.x = -0.15;
|
|
scene.add( container );
|
|
|
|
//////////////
|
|
// TEXT PANEL
|
|
//////////////
|
|
|
|
const textPanel = new ThreeMeshUI.Block( {
|
|
fontFamily: FontJSON,
|
|
fontTexture: FontImage,
|
|
width: 1,
|
|
height: 0.32,
|
|
padding: 0.02,
|
|
backgroundColor: new THREE.Color( colors.panelBack ),
|
|
backgroundOpacity: 1
|
|
} );
|
|
|
|
textPanel.position.set( 0, -0.15, 0 );
|
|
container.add( textPanel );
|
|
|
|
//
|
|
|
|
const title = new ThreeMeshUI.Text( {
|
|
width: 1,
|
|
height: 0.1,
|
|
justifyContent: 'center',
|
|
fontSize: 0.045,
|
|
textContent: 'Type some text on the keyboard'
|
|
} )
|
|
|
|
userText = new ThreeMeshUI.Text( {
|
|
width: 1,
|
|
// height: 0.4,
|
|
fontSize: 0.033,
|
|
padding: 0.02,
|
|
boxSizing: 'border-box',
|
|
textContent: "",
|
|
textAlign: 'center'
|
|
} );
|
|
|
|
// add keyboard input to userText
|
|
const feeder = new PhysicalKeyboardFeederBehavior( userText );
|
|
feeder.attach();
|
|
|
|
textPanel.add( title, userText );
|
|
|
|
////////////////////////
|
|
// LAYOUT OPTIONS PANEL
|
|
////////////////////////
|
|
|
|
// BUTTONS
|
|
|
|
let layoutButtons = [
|
|
[ 'English', QwertyEnglish ],
|
|
[ 'Nordic', QwertyNordic ],
|
|
[ 'German', QwertzDeutsch ],
|
|
[ 'Spanish', QwertySpanish ],
|
|
[ 'French', AzertyFrench ],
|
|
[ 'Russian', QwertyRussian ],
|
|
[ 'Greek', QwertyGreek ]
|
|
];
|
|
|
|
layoutButtons = layoutButtons.map( ( options ) => {
|
|
|
|
const button = new ThreeMeshUI.Text( {
|
|
|
|
name: 'switch keyboard layout to '+options[ 0 ],
|
|
// height: 0.06,
|
|
width: 'auto',
|
|
padding : '0.01 0.05',
|
|
margin: 0.012,
|
|
justifyContent: 'center',
|
|
// backgroundColor: new THREE.Color( colors.button ),
|
|
backgroundColor: colors.button ,
|
|
offset: 0.01,
|
|
backgroundOpacity: 1,
|
|
fontSize: 0.035,
|
|
textContent: options[ 0 ]
|
|
} );
|
|
|
|
new SimpleStateBehavior( button, {
|
|
hover: {
|
|
offset: 0.02,
|
|
backgroundColor: colors.hovered,
|
|
},
|
|
checked: {
|
|
backgroundColor: colors.selected,
|
|
},
|
|
});
|
|
|
|
|
|
button.addEventListener( 'click' , () => {
|
|
|
|
if ( keyboard ) {
|
|
|
|
interactiveRaycaster.removeObject( keyboard );
|
|
keyboard.clear();
|
|
|
|
keyboardSync.detach();
|
|
keyboardSync = null;
|
|
|
|
}
|
|
|
|
for ( let i = 0; i < layoutButtons.length; i++ ) {
|
|
const layoutButton = layoutButtons[ i ];
|
|
layoutButton.deactivatePseudoState('checked');
|
|
}
|
|
|
|
button.activatePseudoState('checked');
|
|
|
|
makeKeyboard( options[ 1 ] );
|
|
|
|
});
|
|
|
|
interactiveRaycaster.addObject( button );
|
|
|
|
return button;
|
|
|
|
} );
|
|
|
|
// CONTAINER
|
|
|
|
layoutOptions = new ThreeMeshUI.Block( {
|
|
fontFamily: FontJSON,
|
|
fontTexture: FontImage,
|
|
padding: 0.02,
|
|
width: 1,
|
|
// offset: 0,
|
|
backgroundColor: new THREE.Color( colors.panelBack ),
|
|
backgroundOpacity: 1
|
|
} ).add(
|
|
new ThreeMeshUI.Text( {
|
|
// offset: 0,
|
|
justifyContent: 'center',
|
|
backgroundOpacity: 0,
|
|
fontSize: 0.04,
|
|
textContent: 'Select a keyboard layout :'
|
|
} ),
|
|
|
|
new ThreeMeshUI.Block( {
|
|
height: 0.075,
|
|
width: 1,
|
|
// offset: 0,
|
|
flexDirection: 'row',
|
|
justifyContent: 'center',
|
|
backgroundOpacity: 0
|
|
} ).add(
|
|
layoutButtons[ 0 ],
|
|
layoutButtons[ 1 ],
|
|
layoutButtons[ 2 ],
|
|
layoutButtons[ 3 ]
|
|
),
|
|
|
|
new ThreeMeshUI.Block( {
|
|
height: 0.075,
|
|
width: 1,
|
|
// offset: 0,
|
|
flexDirection: 'row',
|
|
justifyContent: 'center',
|
|
backgroundOpacity: 0
|
|
} ).add(
|
|
layoutButtons[ 4 ],
|
|
layoutButtons[ 5 ],
|
|
layoutButtons[ 6 ]
|
|
)
|
|
);
|
|
|
|
layoutOptions.position.set( 0, 0.2, 0 );
|
|
container.add( layoutOptions );
|
|
|
|
// Set English button as selected from the start
|
|
makeKeyboard();
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
Create a keyboard UI with three-mesh-ui, and assign states to each keys.
|
|
Three-mesh-ui strictly provides user interfaces, with tools to manage
|
|
UI state (component.setupState and component.setState).
|
|
|
|
It does not handle interacting with the UI. The reason for that is simple :
|
|
with webXR, the number of way a mesh can be interacted had no limit. Therefore,
|
|
this is left to the user. three-mesh-ui components are THREE.Object3Ds, so
|
|
you might want to refer to three.js documentation to know how to interact with objects.
|
|
|
|
If you want to get started quickly, just copy and paste this example, it manages
|
|
mouse and touch interaction, and VR controllers pointing rays.
|
|
|
|
*/
|
|
|
|
function makeKeyboard( layout = null ) {
|
|
|
|
keyboard = new KeyboardElement( {
|
|
// the default layout to display
|
|
layout : layout ? layout : QwertyEnglish,
|
|
// can detect automatically which layout to use
|
|
autoDetectLayout : !layout,
|
|
// between this list of keyboard layouts
|
|
availableLayouts : [QwertyEnglish,AzertyFrench,QwertzDeutsch, QwertyGreek, QwertyNordic, QwertyRussian],
|
|
|
|
fontFamily: FontJSON,
|
|
fontTexture: FontImage,
|
|
fontSize: 0.035, // fontSize will propagate to the keys blocks
|
|
backgroundColor: new THREE.Color( colors.keyboardBack ),
|
|
backgroundOpacity: 1,
|
|
backspaceTexture: Backspace,
|
|
shiftTexture: Shift,
|
|
enterTexture: Enter,
|
|
keyOptions : {
|
|
color: 0xffffff,
|
|
backgroundColor: colors.button,
|
|
backgroundOpacity:1,
|
|
borderRadius: 0.01,
|
|
offset: 0.0085
|
|
}
|
|
} );
|
|
|
|
keyboard.position.set( 0, 0.88, -1 );
|
|
keyboard.rotation.x = -0.55;
|
|
|
|
keyboard.interactiveListener.bindText( userText )
|
|
|
|
scene.add( keyboard );
|
|
|
|
interactiveRaycaster.addObject( keyboard );
|
|
|
|
// Report physicalKeyboard even to virtual keyboard
|
|
keyboardSync = new KeyboardsSynchronizerBehavior( keyboard );
|
|
keyboardSync.attach();
|
|
|
|
|
|
// styles for keys
|
|
keyboard.keys.forEach( ( key ) => {
|
|
|
|
new SimpleStateBehavior(key,{
|
|
hover: {
|
|
backgroundColor: colors.hovered,
|
|
backgroundOpacity: 1,
|
|
},
|
|
checked:{
|
|
backgroundColor: 0x00ff99
|
|
},
|
|
active: {
|
|
offset: 0.0025,
|
|
backgroundColor: colors.hovered,
|
|
}
|
|
});
|
|
|
|
});
|
|
}
|
|
|
|
//
|
|
|
|
function onWindowResize() {
|
|
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
}
|
|
|
|
//
|
|
|
|
function loop() {
|
|
|
|
// Don't forget, ThreeMeshUI must be updated manually.
|
|
// This has been introduced in version 3.0.0 in order
|
|
// to improve performance
|
|
ThreeMeshUI.update();
|
|
controls.update();
|
|
interactiveRaycaster.update();
|
|
|
|
stats.update();
|
|
|
|
|
|
|
|
renderer.render( scene, camera );
|
|
|
|
}
|