/* eslint-disable no-prototype-builtins */ import AFRAME from 'aframe' const THREE = AFRAME.THREE export const ComponentName = 'highlight' export interface Data { [key: string]: any, hover: boolean, active: boolean, disabled: boolean, color: number, hoverColor: number, activeColor: number, disabledColor: number, type: string, target: string, bordersize: number, borderbaseopacity: number, disabledopacity: number, createborder: boolean, bordername: string, } export const ComponentSchema: AFRAME.MultiPropertySchema = { hover: { default: false }, active: { default: false }, disabled: { default: false }, color: { default: 0x484848 }, hoverColor: { default: 0x04FF5F }, activeColor: { default: 0xFFD704 }, disabledColor: { default: 0xA9A9A9 }, type: { default: 'color' }, // oneOf: ['color', 'border', 'text'] target: { default: '', type: 'string' }, bordersize: { default: 0.05 }, borderbaseopacity: { default: 0 }, disabledopacity: { default: 0.2 }, createborder: { default: false }, bordername: { default: 'border' } } export interface Props { initHighlight: () => void, addHandlers: () => void, removeHandlers: () => void, aHandler: () => void, intersectingRaycaster: any, intersection: any, createBorder: () => void, createBorderSphere: (geomAttribute: any) => void, createBorderPlane: (geomAttribute: any) => void, updateColor: (meshName: string) => void, updateTextColor: () => void, handleIntersection: (attribute: string) => void, raycasterIntersectedHandler: (evt: any) => void, raycasterIntersectedClearedHandler: (evt: any) => void, mousedownHandler: (evt: any) => void, mouseupHandler: (evt: any) => void } export const Component: AFRAME.ComponentDefinition = { schema: ComponentSchema, data: { } as Data, intersectingRaycaster: null, intersection: null, init () { if (this.el.sceneEl?.hasLoaded) this.initHighlight() else this.el.sceneEl?.addEventListener('loaded', this.initHighlight.bind(this)) }, play() { if (!this.data.disabled) { this.addHandlers() } }, pause() { if (!this.data.disabled) { this.removeHandlers() } }, tick() { this.handleIntersection('hover') }, update(oldData: Data) { const data = this.data const changedData = Object.keys(this.data).filter(x => this.data[x] !== oldData[x]) if (changedData.includes('disabled')) { this.el.setAttribute('highlight', { hover: false, active: false }) } if (['hover', 'active', 'disabled'].some(prop => changedData.includes(prop))) { if (data.type === 'color') { this.updateColor('mesh') } else if (data.type === 'text') { this.updateTextColor() } else if (data.type === 'border' && this.el.object3DMap.hasOwnProperty(this.data.bordername)) { this.updateColor(this.data.bordername) } } if (changedData.includes('disabled')) { if (!data.disabled) { this.addHandlers() } else { this.removeHandlers() } } }, initHighlight() { if (this.data.createborder) { this.createBorder() } }, aHandler() { }, addHandlers: function() { this.el.addEventListener('raycaster-intersected', this.raycasterIntersectedHandler.bind(this)) this.el.addEventListener('raycaster-intersected-cleared', this.raycasterIntersectedClearedHandler.bind(this)) this.el.addEventListener('mousedown', this.mousedownHandler.bind(this)) this.el.addEventListener('mouseup', this.mouseupHandler.bind(this)) }, removeHandlers: function() { this.el.removeEventListener('raycaster-intersected', this.raycasterIntersectedHandler) this.el.removeEventListener('raycaster-intersected-cleared', this.raycasterIntersectedClearedHandler) this.el.removeEventListener('mousedown', this.mousedownHandler) this.el.removeEventListener('mouseup', this.mouseupHandler) }, createBorder() { const self = this const data = self.data let geomAttribute if (data.target === '') { geomAttribute = self.el.getAttribute('geometry') } else { // eslint-disable-next-line no-prototype-builtins if (self.el.object3DMap.hasOwnProperty(data.target)) { const geo = (self.el.getObject3D(data.target) as THREE.Mesh).geometry geomAttribute = (geo as any).parameters if (geo.type === 'PlaneBufferGeometry') { geomAttribute.primitive = 'plane' geomAttribute.buffer = true geomAttribute.skipCache = false geomAttribute.segmentsHeight = geomAttribute.heightSegments || 1 geomAttribute.segmentsWidth = geomAttribute.widthSegments || 1 } } else { self.el.addEventListener('media-mesh-set', () => { self.createBorder() }) return } } if (geomAttribute) { switch (geomAttribute.primitive) { case 'sphere': self.createBorderSphere(geomAttribute) break case 'plane': self.createBorderPlane(geomAttribute) break default: break } } }, createBorderSphere(geomAttribute) { const self = this const data = self.data const borderGeomAttribute = Object.assign({}, geomAttribute) borderGeomAttribute.radius = borderGeomAttribute.radius * (1 + data.bordersize) // (this.system as AFRAME.SystemDefinition) const geom = (self.el.sceneEl?.systems.geometry as any).getOrCreateGeometry(borderGeomAttribute) const color = data.active ? data.activeColor : data.hover ? data.hoverColor : data.color const opacity = data.active || data.hover ? 1 : data.borderbaseopacity const transparent = !(data.active || data.hover) const mat = new THREE.MeshBasicMaterial({ color: new THREE.Color(color), side: THREE.BackSide, opacity: opacity, transparent: transparent }) const newMesh = new THREE.Mesh(geom, mat) newMesh.name = this.data.bordername newMesh.updateMatrix() self.el.setObject3D(this.data.bordername, newMesh) }, createBorderPlane(geomAttribute) { const self = this const data = Object.assign({}, self.data) const borderGeomAttribute = Object.assign({}, geomAttribute) borderGeomAttribute.width = borderGeomAttribute.width * (1 + data.bordersize) borderGeomAttribute.height = borderGeomAttribute.height * (1 + data.bordersize) const cache = (self.el.sceneEl?.systems.geometry as any).cache const hash = (self.el.sceneEl?.systems.geometry as any).hash(borderGeomAttribute) const isCached = !!cache[hash] const geom = (self.el.sceneEl?.systems.geometry as any).getOrCreateGeometry(borderGeomAttribute) if (!isCached) { geom.translate(0, 0, -0.001) } const color = data.active ? data.activeColor : data.hover ? data.hoverColor : data.color const opacity = data.active || data.hover ? 1 : data.borderbaseopacity const transparent = !(data.active || data.hover) const mat = new THREE.MeshBasicMaterial({ color: new THREE.Color(color), side: THREE.FrontSide, opacity: opacity, transparent: transparent }) const mesh = new THREE.Mesh(geom, mat) mesh.name = this.data.bordername self.el.setObject3D(this.data.bordername, mesh) }, updateColor(meshName = 'mesh') { const self = this const data = self.data const newColor: number = data.disabled ? data.disabledColor : data.active ? data.activeColor : data.hover ? data.hoverColor : data.color const opacity = data.disabled ? data.disabledopacity : data.active || data.hover ? 1 : data.borderbaseopacity const transparent = data.disabled ? true : !(data.active || data.hover) const mesh = self.el.getObject3D(meshName) as THREE.Mesh if (mesh) { const mat = mesh.material as THREE.MeshBasicMaterial mat.color = new THREE.Color(newColor) mat.opacity = opacity mat.transparent = transparent } }, updateTextColor() { const self = this const data = self.data const newColor = data.disabled ? data.disabledColor : data.active ? data.activeColor : data.hover ? data.hoverColor : data.color const txtObj = this.el.getObject3D('text') if (txtObj && (txtObj as any).material) { (txtObj as any).material.uniforms.color.value = new THREE.Color(newColor) } }, handleIntersection(attribute = 'hover') { const self = this if (!this.intersectingRaycaster) { return } const value = {} as any value[attribute] = true const intersection = this.intersectingRaycaster.getIntersection(this.el) self.intersection = intersection if (intersection && !self.data[attribute]) { if (self.data.target !== '') { switch (intersection.object.name) { case self.data.target: case this.data.bordername: self.el.setAttribute('highlight', value) break default: break } } else { self.el.setAttribute('highlight', value) } } }, raycasterIntersectedHandler(evt) { this.intersectingRaycaster = evt.detail.el.components.raycaster }, raycasterIntersectedClearedHandler(evt) { console.debug(evt) if (this.intersectingRaycaster != null) { const intersection = this.intersectingRaycaster.getIntersection(this.el) if (intersection === undefined) { this.intersectingRaycaster = null } else { } } else { // console.log('self.intersectingRaycaster is null') } if (this.data.hover) { this.el.setAttribute('highlight', { hover: false }) } }, mousedownHandler(evt) { console.debug(evt) this.handleIntersection('active') }, mouseupHandler(evt) { console.debug(evt) if (this.el.getAttribute('highlight').active) { this.el.setAttribute('highlight', { active: false }) } } } export const Primitive: AFRAME.PrimitiveDefinition = { defaultComponents: { ComponentName: {} }, deprecated: false, mappings: { id: ComponentName + '.id', 'some-date': ComponentName + '.someData' } } const ComponentSystem = { name: ComponentName, // system: SystemDef, component: Component, primitive: Primitive } export default ComponentSystem