AFRAME.registerComponent('trail', { schema: { color: {type: 'color'}, enabled: {default: false}, hand: {type: 'string'} }, init: function () { var geometry = this.geometry = new THREE.BufferGeometry(); var maxPoints = this.maxPoints = 12; var vertices = this.vertices = new Float32Array(36 * maxPoints); var colors = this.colors = new Float32Array(48 * maxPoints); var bladeColor = this.bladeColor = new THREE.Color(this.data.color); this.bladeColor = { red: bladeColor.r, green: bladeColor.g, blue: bladeColor.b, alpha: 1.0 }; this.saberEl = this.el.querySelector('.blade'); this.layers = 0; this.saberTrajectory = [ {top: new THREE.Vector3(-0.5, 0, 0), center: new THREE.Vector3(0, 0, 0), bottom: new THREE.Vector3(0.5, 0, 0)}, {top: new THREE.Vector3(-0.5, 0.5, 0), center: new THREE.Vector3(0, 0.5, 0), bottom: new THREE.Vector3(0.5, 0.5, 0)}, {top: new THREE.Vector3(-0.5, 1.0, 0), center: new THREE.Vector3(0, 1.0, 0), bottom: new THREE.Vector3(0.5, 1.0, 0)} ]; geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3).setDynamic(true)); geometry.addAttribute('vertexColor', new THREE.BufferAttribute(colors, 4).setDynamic(true)); const material = new THREE.ShaderMaterial({ side: THREE.DoubleSide, vertexColors: THREE.VertexColors, transparent: true, blending: THREE.AdditiveBlending, vertexShader: [ 'varying vec4 vColor;', 'attribute vec4 vertexColor;', 'void main() {', 'vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);', 'vColor = vertexColor;', 'gl_Position = projectionMatrix * modelViewPosition;', '}' ].join(''), fragmentShader: [ 'varying vec4 vColor;', 'void main() {', 'gl_FragColor = vColor;', '}' ].join('') }); const mesh = this.mesh = new THREE.Mesh(geometry, material); mesh.frustumCulled = false; mesh.vertices = vertices; this.el.sceneEl.setObject3D(`trail__${this.data.hand}`, mesh); }, update: function (oldData) { if (!oldData.enabled && this.data.enabled) { this.enabledTime = this.el.sceneEl.time; this.mesh.visible = false; } if (oldData.enabled && !this.data.enabled) { this.mesh.visible = false; } }, tock: function (time, delta) { if (!this.data.enabled) { return; } // Delay before showing after enabled to prevent flash from old saber position. if (!this.mesh.visible && time > this.enabledTime + 250) { this.mesh.visible = true; } this.sampleSaberPosition(); }, addLayer: function (length) { var startX = -1.0; var segments = this.segments; var dx = 2 / segments; var colors = this.colors; var vertices = this.vertices; var color = this.bladeColor; if (this.layers >= this.maxLayers) { this.layers = 0; } const bottomLayer = this.layers * length; length = bottomLayer + length; const indexOffset = this.layers * segments * 18; const colorOffset = this.layers * segments * 24; for (var i = 0; i < segments; ++i) { vertices[indexOffset + 18 * i] = startX + i * dx; vertices[indexOffset + 18 * i + 1] = bottomLayer; vertices[indexOffset + 18 * i + 2] = 0.0; // Color colors[colorOffset + 24 * i] = color.red; colors[colorOffset + 24 * i + 1] = color.green; colors[colorOffset + 24 * i + 2] = color.blue; colors[colorOffset + 24 * i + 3] = color.alpha; vertices[indexOffset + 18 * i + 3] = startX + i * dx; vertices[indexOffset + 18 * i + 4] = length; vertices[indexOffset + 18 * i + 5] = 0.0; // Color colors[colorOffset + 24 * i + 4] = color.red; colors[colorOffset + 24 * i + 5] = color.green; colors[colorOffset + 24 * i + 6] = color.blue; colors[colorOffset + 24 * i + 7] = color.alpha; vertices[indexOffset + 18 * i + 6] = startX + i * dx + dx; vertices[indexOffset + 18 * i + 7] = length; vertices[indexOffset + 18 * i + 8] = 0.0; // Color colors[colorOffset + 24 * i + 8] = color.red; colors[colorOffset + 24 * i + 9] = color.green; colors[colorOffset + 24 * i + 10] = color.blue; colors[colorOffset + 24 * i + 11] = color.alpha; vertices[indexOffset + 18 * i + 9] = startX + i * dx + dx; vertices[indexOffset + 18 * i + 10] = bottomLayer; vertices[indexOffset + 18 * i + 11] = 0.0; // Color colors[colorOffset + 24 * i + 12] = color.red; colors[colorOffset + 24 * i + 13] = color.green; colors[colorOffset + 24 * i + 14] = color.blue; colors[colorOffset + 24 * i + 15] = color.alpha; vertices[indexOffset + 18 * i + 12] = startX + i * dx; vertices[indexOffset + 18 * i + 13] = bottomLayer; vertices[indexOffset + 18 * i + 14] = 0.0; // Color colors[colorOffset + 24 * i + 16] = color.red; colors[colorOffset + 24 * i + 17] = color.green; colors[colorOffset + 24 * i + 18] = color.blue; colors[colorOffset + 24 * i + 19] = color.alpha; vertices[indexOffset + 18 * i + 15] = startX + i * dx + dx; vertices[indexOffset + 18 * i + 16] = length; vertices[indexOffset + 18 * i + 17] = 0.0; // Color colors[colorOffset + 24 * i + 20] = color.red; colors[colorOffset + 24 * i + 21] = color.green; colors[colorOffset + 24 * i + 22] = color.blue; colors[colorOffset + 24 * i + 23] = color.alpha; } this.layers++; this.geometry.attributes.position.needsUpdate = true; this.geometry.attributes.vertexColor.needsUpdate = true; this.geometry.attributes.uv.needsUpdate = true; }, initGeometry: function () { var i; var previousPoint; var currentPoint; var saberTrajectory = this.saberTrajectory; var vertices = this.geometry.attributes.position.array; var colors = this.geometry.attributes.vertexColor.array; var color = this.bladeColor; var alpha; var previousAlpha; for (i = 1; i < saberTrajectory.length; i++) { if (i === 1) { previousAlpha = alpha; } alpha = 1.0 - ((saberTrajectory.length - i) / saberTrajectory.length); currentPoint = saberTrajectory[i]; previousPoint = saberTrajectory[i - 1]; vertices[36 * i] = previousPoint.center.x; vertices[36 * i + 1] = previousPoint.center.y; vertices[36 * i + 2] = previousPoint.center.z; // Color colors[48 * i] = color.red; colors[48 * i + 1] = color.green; colors[48 * i + 2] = color.blue; colors[48 * i + 3] = previousAlpha * 0.2; vertices[36 * i + 3] = currentPoint.top.x; vertices[36 * i + 4] = currentPoint.top.y; vertices[36 * i + 5] = currentPoint.top.z; // Color colors[48 * i + 4] = color.red; colors[48 * i + 5] = color.green; colors[48 * i + 6] = color.blue; colors[48 * i + 7] = alpha; vertices[36 * i + 6] = previousPoint.top.x; vertices[36 * i + 7] = previousPoint.top.y; vertices[36 * i + 8] = previousPoint.top.z; // Color colors[48 * i + 8] = color.red; colors[48 * i + 9] = color.green; colors[48 * i + 10] = color.blue; colors[48 * i + 11] = previousAlpha; vertices[36 * i + 9] = previousPoint.center.x; vertices[36 * i + 10] = previousPoint.center.y; vertices[36 * i + 11] = previousPoint.center.z; // Color colors[48 * i + 12] = color.red; colors[48 * i + 13] = color.green; colors[48 * i + 14] = color.blue; colors[48 * i + 15] = previousAlpha * 0.2; vertices[36 * i + 12] = currentPoint.center.x; vertices[36 * i + 13] = currentPoint.center.y; vertices[36 * i + 14] = currentPoint.center.z; // Color colors[48 * i + 16] = color.red; colors[48 * i + 17] = color.green; colors[48 * i + 18] = color.blue; colors[48 * i + 19] = alpha * 0.2; vertices[36 * i + 15] = currentPoint.top.x; vertices[36 * i + 16] = currentPoint.top.y; vertices[36 * i + 17] = currentPoint.top.z; // Color colors[48 * i + 20] = color.red; colors[48 * i + 21] = color.green; colors[48 * i + 22] = color.blue; colors[48 * i + 23] = alpha; vertices[36 * i + 18] = previousPoint.bottom.x; vertices[36 * i + 19] = previousPoint.bottom.y; vertices[36 * i + 20] = previousPoint.bottom.z; // Color colors[48 * i + 24] = color.red; colors[48 * i + 25] = color.green; colors[48 * i + 26] = color.blue; colors[48 * i + 27] = 0.0; vertices[36 * i + 21] = currentPoint.center.x; vertices[36 * i + 22] = currentPoint.center.y; vertices[36 * i + 23] = currentPoint.center.z; // Color colors[48 * i + 28] = color.red; colors[48 * i + 29] = color.green; colors[48 * i + 30] = color.blue; colors[48 * i + 31] = alpha * 0.2; vertices[36 * i + 24] = previousPoint.center.x; vertices[36 * i + 25] = previousPoint.center.y; vertices[36 * i + 26] = previousPoint.center.z; // Color colors[48 * i + 32] = color.red; colors[48 * i + 33] = color.green; colors[48 * i + 34] = color.blue; colors[48 * i + 35] = previousAlpha * 0.2; vertices[36 * i + 27] = previousPoint.bottom.x; vertices[36 * i + 28] = previousPoint.bottom.y; vertices[36 * i + 29] = previousPoint.bottom.z; // Color colors[48 * i + 36] = color.red; colors[48 * i + 37] = color.green; colors[48 * i + 38] = color.blue; colors[48 * i + 39] = 0.0; vertices[36 * i + 30] = currentPoint.bottom.x; vertices[36 * i + 31] = currentPoint.bottom.y; vertices[36 * i + 32] = currentPoint.bottom.z; // Color colors[48 * i + 40] = color.red; colors[48 * i + 41] = color.green; colors[48 * i + 42] = color.blue; colors[48 * i + 43] = 0.0; vertices[36 * i + 33] = currentPoint.center.x; vertices[36 * i + 34] = currentPoint.center.y; vertices[36 * i + 35] = currentPoint.center.z; // Color colors[48 * i + 44] = color.red; colors[48 * i + 45] = color.green; colors[48 * i + 46] = color.blue; colors[48 * i + 47] = alpha * 0.2; previousAlpha = alpha; } this.geometry.attributes.position.needsUpdate = true; this.geometry.attributes.vertexColor.needsUpdate = true; }, sampleSaberPosition: function () { var saberEl = this.saberEl; var saberObject; var sample; if (this.saberTrajectory.length === this.maxPoints) { // Dump oldest point. sample = this.saberTrajectory.shift(); sample.top.set(0, -0.4, 0); sample.center.set(0, 0, 0); sample.bottom.set(0, 0.4, 0); } else { sample = { top: new THREE.Vector3(0, -0.4, 0), center: new THREE.Vector3(0, 0, 0), bottom: new THREE.Vector3(0, 0.4, 0) }; } saberObject = saberEl.object3D; saberObject.parent.updateMatrixWorld(); saberObject.localToWorld(sample.top); saberObject.localToWorld(sample.center); saberObject.localToWorld(sample.bottom); this.saberTrajectory.push(sample); this.initGeometry(); } });