a3model
Version:
Accessibility for Three.js models
157 lines (148 loc) • 6.84 kB
JavaScript
import * as THREE from 'three';
import {
CSS2DRenderer,
CSS2DObject,
} from 'three/examples/jsm/renderers/CSS2DRenderer'
class A3{
constructor(canvas, renderer, a3canvas, sizes) {
this.canvas = canvas
this.renderer = renderer;
this.sizes = sizes;
this.labelRenderer = new CSS2DRenderer();
this.labelRenderer.setSize(this.sizes.width, this.sizes.height)
a3canvas.appendChild(this.labelRenderer.domElement);
this.raycaster = new THREE.Raycaster()
this.meshList = []
this.annotationDivList = []
this.clickList = []
this.hoverList = []
this.disposed = false
}
render(scene, camera){
this.labelRenderer.domElement.style.top = this.canvas.offsetTop;
this.labelRenderer.domElement.style.position = 'absolute';
this.labelRenderer.domElement.style.pointerEvents = 'none';
this.labelRenderer.render(scene, camera);
}
createBox(mesh, role=null){
const annotationDiv = document.createElement('div')
annotationDiv.className = 'annotationLabelClass'
annotationDiv.id = mesh.name
annotationDiv.tabIndex = '0'
if (role != null){
annotationDiv.setAttribute('role', role)
}
const annotationLabel = new CSS2DObject(annotationDiv)
annotationLabel.position.set(0, 0, 0)
mesh.add(annotationLabel)
this.annotationDivList.push(annotationDiv)
this.meshList.push(mesh)
return mesh
}
updateBoxes(camera){
for (let i=0; i<this.annotationDivList.length; i++){
let size = new THREE.Vector3()
let boundingBox = new THREE.Box3().setFromObject(this.meshList[i])
boundingBox.getSize(size)
var vFOV = camera.fov * Math.PI / 180;
var h = 2 * Math.tan( vFOV / 2 ) * camera.position.z;
var aspect = this.sizes.width / this.sizes.height;
var w = h * aspect;
const pixelSizeWidth = this.sizes.width * ((1 / w) * size.x)
const pixelSizeHeight = this.sizes.height * ((1 / h) * size.z)
this.annotationDivList[i].style.width = (pixelSizeWidth).toString() + 'px'
this.annotationDivList[i].style.height = (pixelSizeHeight).toString() + 'px'
}
}
functWrapper(funct, ...args){
return function(){ return funct(...args); }
}
click(meshname, funct, description){
this.clickList.push({'mesh': meshname, 'funct': funct, 'description': description})
// this.hoverList.push({'mesh': meshname, 'funct': funct, 'description': description})
}
hover(meshname, funct, description){
this.hoverList.push({'mesh': meshname, 'funct': funct, 'description': description})
}
renderEffects(camera){
document.addEventListener('keydown', (e) => onKeyDown(e, this.clickList, this.hoverList))
function onKeyDown(e, clickList, hoverList) {
let cIndex = clickList.findIndex(m => m.mesh === document.activeElement.id)
let hIndex = hoverList.findIndex(m => m.mesh === document.activeElement.id)
console.log(hoverList)
if (e.keyCode == 9) {
if (hIndex >= 0){
let hoverAnnotation = document.getElementById(hoverList[hIndex+1].mesh)
hoverAnnotation.innerHTML = hoverList[hIndex+1].description
hoverList[hIndex+1].funct()
}
}
if (e.keyCode == 13) {
if (cIndex >= 0){
let clickAnnotation = document.getElementById(clickList[cIndex].mesh)
clickAnnotation.innerHTML = clickList[cIndex].description
setTimeout(function(clickAnnotation){
clickAnnotation.innerHTML = ''
}, 500, clickAnnotation)
clickAnnotation.click()
clickList[cIndex].funct()
}
}
}
this.renderer.domElement.addEventListener('mousemove', (e) => onClick(e, this.clickList, this.hoverList, this.raycaster, this.renderer, this.meshList, camera));
this.renderer.domElement.addEventListener('click', (e) => onClick(e, this.clickList, this.hoverList, this.raycaster, this.renderer, this.meshList, camera));
this.renderer.domElement.addEventListener('touchstart', (e) => onClick(e, this.clickList, this.hoverList, this.raycaster, this.renderer, this.meshList, camera));
function onClick(e, clickList, hoverList, raycaster, renderer, meshList, camera) {
if (e.type == "touchstart"){
e.clientX = e.touches[0].pageX;
e.clientY = e.touches[0].pageY;
}
raycaster.setFromCamera(
{
x: (e.offsetX / renderer.domElement.clientWidth) * 2 - 1,
y: -(e.offsetY / renderer.domElement.clientHeight) * 2 + 1,
},
camera
);
const intersects = raycaster.intersectObjects(meshList);
if (intersects.length > 0) {
document.body.style.cursor = 'pointer'
for (let i=0; i<clickList.length; i++){
let clickAnnotation = document.getElementById(clickList[i].mesh)
if (e.type == "click"){
if (intersects[0].object.name == clickList[i].mesh){
clickList[i].funct()
clickAnnotation.innerHTML = clickList[i].description
setTimeout(function(clickAnnotation){
clickAnnotation.innerHTML = ''
}, 500, clickAnnotation)
clickAnnotation.focus()
}
}
let hIndex = hoverList.findIndex(m => m.mesh === intersects[0].object.name)
if (hIndex > -1){
let hoverAnnotation = document.getElementById(hoverList[i].mesh)
hoverList[hIndex].funct()
hoverAnnotation.innerHTML = hoverList[hIndex].description
hoverAnnotation.click()
}
}
} else {
document.body.style.cursor = 'default'
}
}
}
dispose(){
this.labelRenderer.dispose()
}
remove(mesh){
document.getElementById(mesh.name).remove();
for (let i = 0; i < this.annotationDivList.length; i++) {
if (this.annotationDivList[i].id === mesh.name) {
this.meshList.splice(i, 1);
this.annotationDivList.splice(i, 1);
}
}
}
}
export default A3;