├── screenshot.png ├── examples ├── vive │ ├── tooltip.png │ └── index.html ├── basic │ ├── tooltip.png │ └── index.html └── oculus │ ├── tooltip.png │ └── index.html ├── testt.js ├── tests ├── index.test.js ├── karma.conf.js ├── __init.test.js └── helpers.js ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── index.html ├── index.js └── dist ├── aframe-tooltip-component.min.js └── aframe-tooltip-component.js /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandojsg/aframe-tooltip-component/HEAD/screenshot.png -------------------------------------------------------------------------------- /examples/vive/tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandojsg/aframe-tooltip-component/HEAD/examples/vive/tooltip.png -------------------------------------------------------------------------------- /examples/basic/tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandojsg/aframe-tooltip-component/HEAD/examples/basic/tooltip.png -------------------------------------------------------------------------------- /examples/oculus/tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandojsg/aframe-tooltip-component/HEAD/examples/oculus/tooltip.png -------------------------------------------------------------------------------- /testt.js: -------------------------------------------------------------------------------- 1 | var list = [ 2 | [ -0.011, 0, 0.058 ], 3 | [ -0.017, -0.008, 0.047 ], 4 | [ 0.003, 0.0013, 0.062 ], 5 | [ 0.0014, -0.006, 0.046 ], 6 | [ -0.0062, -0.033, 0.048 ], 7 | [ -0.007, -0.008, 0.087 ] 8 | ]; 9 | 10 | var dst = [-0.009, -0.008, 0.047]; 11 | 12 | var diff = [ dst[0] - list[0][0], dst[1] - list[0][1], dst[2] - list[0][1]]; 13 | 14 | for (var i=0;i 26 | My A-Frame Scene 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ``` 37 | 38 | 39 | 48 | 49 | #### npm 50 | 51 | Install via npm: 52 | 53 | ```bash 54 | npm install aframe-tooltip-component 55 | ``` 56 | 57 | Then require and use. 58 | 59 | ```js 60 | require('aframe'); 61 | require('aframe-tooltip-component'); 62 | ``` 63 | -------------------------------------------------------------------------------- /tests/helpers.js: -------------------------------------------------------------------------------- 1 | /* global suite */ 2 | 3 | /** 4 | * Helper method to create a scene, create an entity, add entity to scene, 5 | * add scene to document. 6 | * 7 | * @returns {object} An `` element. 8 | */ 9 | module.exports.entityFactory = function (opts) { 10 | var scene = document.createElement('a-scene'); 11 | var assets = document.createElement('a-assets'); 12 | var entity = document.createElement('a-entity'); 13 | scene.appendChild(assets); 14 | scene.appendChild(entity); 15 | 16 | opts = opts || {}; 17 | 18 | if (opts.assets) { 19 | opts.assets.forEach(function (asset) { 20 | assets.appendChild(asset); 21 | }); 22 | } 23 | 24 | document.body.appendChild(scene); 25 | return entity; 26 | }; 27 | 28 | /** 29 | * Creates and attaches a mixin element (and an `` element if necessary). 30 | * 31 | * @param {string} id - ID of mixin. 32 | * @param {object} obj - Map of component names to attribute values. 33 | * @param {Element} scene - Indicate which scene to apply mixin to if necessary. 34 | * @returns {object} An attached `` element. 35 | */ 36 | module.exports.mixinFactory = function (id, obj, scene) { 37 | var mixinEl = document.createElement('a-mixin'); 38 | mixinEl.setAttribute('id', id); 39 | Object.keys(obj).forEach(function (componentName) { 40 | mixinEl.setAttribute(componentName, obj[componentName]); 41 | }); 42 | 43 | var assetsEl = scene ? scene.querySelector('a-assets') : document.querySelector('a-assets'); 44 | assetsEl.appendChild(mixinEl); 45 | 46 | return mixinEl; 47 | }; 48 | 49 | /** 50 | * Test that is only run locally and is skipped on CI. 51 | */ 52 | module.exports.getSkipCISuite = function () { 53 | if (window.__env__.TEST_ENV === 'ci') { 54 | return suite.skip; 55 | } else { 56 | return suite; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-tooltip-component", 3 | "version": "0.1.2", 4 | "description": "A Tooltip component for A-Frame.", 5 | "main": "index.js", 6 | "cdn": "dist/aframe-tooltip-component.min.js", 7 | "scripts": { 8 | "dev": "budo index.js:dist/aframe-tooltip-component.min.js --port 7000 --live --open", 9 | "dist": "webpack index.js dist/aframe-tooltip-component.js && webpack -p index.js dist/aframe-tooltip-component.min.js", 10 | "lint": "semistandard -v | snazzy", 11 | "prepublish": "npm run dist", 12 | "ghpages": "ghpages", 13 | "start": "npm run dev", 14 | "test": "karma start ./tests/karma.conf.js", 15 | "test:firefox": "karma start ./tests/karma.conf.js --browsers Firefox", 16 | "test:chrome": "karma start ./tests/karma.conf.js --browsers Chrome" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/fernandojsg/aframe-tooltip-component.git" 21 | }, 22 | "keywords": [ 23 | "aframe", 24 | "aframe-component", 25 | "aframe-vr", 26 | "vr", 27 | "mozvr", 28 | "webvr", 29 | "tooltip" 30 | ], 31 | "author": "Fernando Serrano ", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/fernandojsg/aframe-tooltip-component/issues" 35 | }, 36 | "homepage": "https://github.com/fernandojsg/aframe-tooltip-component#readme", 37 | "devDependencies": { 38 | "browserify": "^13.0.0", 39 | "budo": "^8.2.2", 40 | "chai": "^3.4.1", 41 | "chai-shallow-deep-equal": "^1.3.0", 42 | "ghpages": "^0.0.8", 43 | "karma": "^0.13.15", 44 | "karma-browserify": "^4.4.2", 45 | "karma-chai-shallow-deep-equal": "0.0.4", 46 | "karma-chrome-launcher": "2.0.0", 47 | "karma-env-preprocessor": "^0.1.1", 48 | "karma-firefox-launcher": "^0.1.7", 49 | "karma-mocha": "^0.2.1", 50 | "karma-mocha-reporter": "^1.1.3", 51 | "karma-sinon-chai": "^1.1.0", 52 | "mocha": "^2.3.4", 53 | "randomcolor": "^0.4.4", 54 | "semistandard": "^8.0.0", 55 | "shelljs": "^0.7.0", 56 | "sinon": "^1.17.5", 57 | "sinon-chai": "^2.8.0", 58 | "shx": "^0.1.1", 59 | "snazzy": "^4.0.0", 60 | "webpack": "^1.13.0" 61 | }, 62 | "semistandard": { 63 | "ignore": [ 64 | "examples/build.js", 65 | "dist/**" 66 | ] 67 | }, 68 | "dependencies": { 69 | "aframe-slice9-component": "^1.0.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | A-Frame Tooltip Component - Basic 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 46 | 47 | 48 |

A-Frame Tooltip Component

49 | 50 |
    51 |
  • 52 | 53 |

    Basic

    54 |

    This is a basic example.

    55 |
  • 56 |
  • 57 | 58 |

    Vive controller

    59 |

    Adding tooltip to the Vive Controller.

    60 |
  • 61 |
  • 62 | 63 |

    Oculus controller

    64 |

    Adding tooltip to the Oculus Controller.

    65 |
  • 66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME */ 2 | require('aframe-slice9-component'); 3 | 4 | if (typeof AFRAME === 'undefined') { 5 | throw new Error('Component attempted to register before AFRAME was available.'); 6 | } 7 | 8 | /** 9 | * Tooltip component for A-Frame. 10 | */ 11 | AFRAME.registerComponent('tooltip', { 12 | schema: { 13 | text: {default: ''}, 14 | end: {type: 'vec3'}, 15 | src: {default: ''}, 16 | rotation: {type: 'vec3'}, 17 | width: {default: 1, min: 0}, 18 | height: {default: 1, min: 0}, 19 | lineColor: {default: '#fff', type: 'color'}, 20 | lineHorizontalAlign: {default: 'left', oneOf: ['left', 'right', 'center']}, 21 | lineVerticalAlign: {default: 'center', oneOf: ['top', 'bottom', 'center']}, 22 | opacity: {default: 1, min: 0, max: 1}, 23 | /* 24 | targetType: {default: 'element', oneOf: ['element', 'position']}, 25 | targetElement: {type: 'selector', if: {targetType: ['element']}}, 26 | */ 27 | targetPosition: {type: 'vec3', if: {targetType: ['position']}} 28 | }, 29 | 30 | /** 31 | * Called once when component is attached. Generally for initial setup. 32 | */ 33 | init: function () { 34 | var el = this.el; 35 | var data = this.data; 36 | 37 | var quad = this.quad = document.createElement('a-entity'); 38 | var self = this; 39 | 40 | quad.addEventListener('loaded', function () { 41 | self.updateTooltip(); 42 | }); 43 | 44 | quad.setAttribute('slice9', {width: data.width, height: data.height, left: 20, right: 43, top: 20, bottom: 43, padding: 0.005, src: data.src}); 45 | quad.setAttribute('rotation', data.rotation); 46 | quad.setAttribute('text', {width: 0.25, color: '#fff', value: data.text, align: 'center'}); 47 | el.appendChild(quad); 48 | 49 | // Line 50 | var material = this.material = new THREE.LineBasicMaterial({color: data.lineColor, opacity: data.opacity, transparent: data.opacity < 1}); 51 | var geometry = this.geometry = new THREE.BufferGeometry(); 52 | geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(2 * 3), 3)); 53 | 54 | this.line = new THREE.Line(geometry, material); 55 | this.el.setObject3D('line', this.line); 56 | 57 | this.el.addEventListener('componentchanged', function (evt) { 58 | self.updateTooltip(); 59 | }); 60 | 61 | this.updateTooltip(); 62 | }, 63 | 64 | /** 65 | * Called when component is attached and when component data changes. 66 | * Generally modifies the entity based on the data. 67 | */ 68 | 69 | updateTooltip: (function () { 70 | var parentPosition = new THREE.Vector3(); 71 | var targetPosition = new THREE.Vector3(); 72 | var startPosition = new THREE.Vector3(); 73 | 74 | return function () { 75 | var data = this.data; 76 | parentPosition.copy(this.el.getAttribute('position')); 77 | targetPosition.copy(data.targetPosition); 78 | 79 | var endPosition = targetPosition.sub(parentPosition); 80 | 81 | var data = this.data; 82 | var valign = {top: data.height / 2, bottom: -data.height / 2, center: 0}; 83 | var halign = {left: -data.width / 2, right: data.width / 2, center: 0}; 84 | 85 | var y = valign[data.lineVerticalAlign]; 86 | var x = halign[data.lineHorizontalAlign]; 87 | 88 | this.quad.setAttribute('slice9', {opacity: data.opacity}); 89 | this.quad.setAttribute('text', {opacity: data.opacity, value: data.text}); 90 | 91 | // Update geometry 92 | this.quad.object3D.updateMatrix(); 93 | startPosition.set(x, y, 0); 94 | startPosition.applyMatrix4(this.quad.object3D.matrix); 95 | 96 | var pos = this.geometry.attributes.position.array; 97 | pos[0] = startPosition.x; 98 | pos[1] = startPosition.y; 99 | pos[2] = startPosition.z; 100 | pos[3] = endPosition.x; 101 | pos[4] = endPosition.y; 102 | pos[5] = endPosition.z; 103 | this.geometry.attributes.position.needsUpdate = true; 104 | 105 | this.material.opacity = data.opacity; 106 | this.material.transparent = data.opacity < 1; 107 | this.material.color.setStyle(data.color); 108 | } 109 | })(), 110 | 111 | update: function () { 112 | this.updateTooltip(); 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /dist/aframe-tooltip-component.min.js: -------------------------------------------------------------------------------- 1 | !function(t){function e(a){if(i[a])return i[a].exports;var r=i[a]={exports:{},id:a,loaded:!1};return t[a].call(r.exports,r,r.exports,e),r.loaded=!0,r.exports}var i={};return e.m=t,e.c=i,e.p="",e(0)}([function(t,e,i){if(i(1),"undefined"==typeof AFRAME)throw new Error("Component attempted to register before AFRAME was available.");AFRAME.registerComponent("tooltip",{schema:{text:{default:""},end:{type:"vec3"},src:{default:""},rotation:{type:"vec3"},width:{default:1,min:0},height:{default:1,min:0},lineColor:{default:"#fff",type:"color"},lineHorizontalAlign:{default:"left",oneOf:["left","right","center"]},lineVerticalAlign:{default:"center",oneOf:["top","bottom","center"]},opacity:{default:1,min:0,max:1},targetPosition:{type:"vec3",if:{targetType:["position"]}}},init:function(){var t=this.el,e=this.data,i=this.quad=document.createElement("a-entity"),a=this;i.addEventListener("loaded",function(){a.updateTooltip()}),i.setAttribute("slice9",{width:e.width,height:e.height,left:20,right:43,top:20,bottom:43,padding:.005,src:e.src}),i.setAttribute("rotation",e.rotation),i.setAttribute("text",{width:.25,color:"#fff",value:e.text,align:"center"}),t.appendChild(i);var r=this.material=new THREE.LineBasicMaterial({color:e.lineColor,opacity:e.opacity,transparent:e.opacity<1}),o=this.geometry=new THREE.BufferGeometry;o.addAttribute("position",new THREE.BufferAttribute(new Float32Array(6),3)),this.line=new THREE.Line(o,r),this.el.setObject3D("line",this.line),this.el.addEventListener("componentchanged",function(t){a.updateTooltip()}),this.updateTooltip()},updateTooltip:function(){var t=new THREE.Vector3,e=new THREE.Vector3,i=new THREE.Vector3;return function(){var a=this.data;t.copy(this.el.getAttribute("position")),e.copy(a.targetPosition);var r=e.sub(t),a=this.data,o={top:a.height/2,bottom:-a.height/2,center:0},n={left:-a.width/2,right:a.width/2,center:0},s=o[a.lineVerticalAlign],l=n[a.lineHorizontalAlign];this.quad.setAttribute("slice9",{opacity:a.opacity}),this.quad.setAttribute("text",{opacity:a.opacity,value:a.text}),this.quad.object3D.updateMatrix(),i.set(l,s,0),i.applyMatrix4(this.quad.object3D.matrix);var d=this.geometry.attributes.position.array;d[0]=i.x,d[1]=i.y,d[2]=i.z,d[3]=r.x,d[4]=r.y,d[5]=r.z,this.geometry.attributes.position.needsUpdate=!0,this.material.opacity=a.opacity,this.material.transparent=a.opacity<1,this.material.color.setStyle(a.color)}}(),update:function(){this.updateTooltip()}})},function(t,e){function i(t){switch(t){case"back":return THREE.BackSide;case"double":return THREE.DoubleSide;default:return THREE.FrontSide}}if("undefined"==typeof AFRAME)throw new Error("Component attempted to register before AFRAME was available.");AFRAME.registerComponent("slice9",{schema:{width:{default:1,min:0},height:{default:1,min:0},left:{default:0,min:0},right:{default:0,min:0},bottom:{default:0,min:0},top:{default:0,min:0},side:{default:"front",oneOf:["front","back","double"]},padding:{default:.1,min:.01},color:{type:"color",default:"#fff"},opacity:{default:1,min:0,max:1},transparent:{default:!0},debug:{default:!1},src:{type:"map"}},multiple:!1,init:function(){var t=this.data,e=this.material=new THREE.MeshBasicMaterial({color:t.color,opacity:t.opacity,transparent:t.transparent,wireframe:t.debug}),i=this.geometry=new THREE.PlaneBufferGeometry(t.width,t.height,3,3);new THREE.TextureLoader;this.plane=new THREE.Mesh(i,e),this.el.setObject3D("mesh",this.plane),this.textureSrc=null},updateMap:function(){function t(t){this.material.map=t,this.material.needsUpdate=!0,this.regenerateMesh()}var e=this.data.src;if(e){if(e===this.textureSrc)return;return this.textureSrc=e,void this.el.sceneEl.systems.material.loadTexture(e,{src:e},t.bind(this))}this.material.map&&t(null)},regenerateMesh:function(){function t(t,e,i){a[3*t]=e,a[3*t+1]=i}function e(t,e,i){r[2*t]=e,r[2*t+1]=i}var i=this.data,a=this.geometry.attributes.position.array,r=this.geometry.attributes.uv.array,o=this.material.map.image;if(o){var n={left:i.left/o.width,right:i.right/o.width,top:i.top/o.height,bottom:i.bottom/o.height};e(1,n.left,1),e(2,n.right,1),e(4,0,n.bottom),e(5,n.left,n.bottom),e(6,n.right,n.bottom),e(7,1,n.bottom),e(8,0,n.top),e(9,n.left,n.top),e(10,n.right,n.top),e(11,1,n.top),e(13,n.left,0),e(14,n.right,0);var s=i.width/2,l=i.height/2,d=-s+i.padding,h=s-i.padding,p=l-i.padding,u=-l+i.padding;t(0,-s,l),t(1,d,l),t(2,h,l),t(3,s,l),t(4,-s,p),t(5,d,p),t(6,h,p),t(7,s,p),t(8,-s,u),t(9,d,u),t(10,h,u),t(11,s,u),t(13,d,-l),t(14,h,-l),t(12,-s,-l),t(15,s,-l),this.geometry.attributes.position.needsUpdate=!0,this.geometry.attributes.uv.needsUpdate=!0}},update:function(t){var e=this.data;this.material.color.setStyle(e.color),this.material.opacity=e.opacity,this.material.transparent=e.transparent,this.material.wireframe=e.debug,this.material.side=i(e.side);var a=AFRAME.utils.diff(e,t);"src"in a?this.updateMap():("width"in a||"height"in a||"padding"in a||"left"in a||"top"in a||"bottom"in a||"right"in a)&&this.regenerateMesh()},remove:function(){},pause:function(){},play:function(){}})}]); -------------------------------------------------------------------------------- /examples/oculus/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | A-Frame Tooltip Component - Basic 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 21 | 22 | 24 | 25 | 27 | 28 | 30 | 31 | 33 | 34 | 35 | 36 | 38 | 39 | 41 | 42 | 44 | 45 | 47 | 48 | 50 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |