diff --git a/src/components/tools/drawing-manager.js b/src/components/tools/drawing-manager.js new file mode 100644 index 0000000000000000000000000000000000000000..cfc1754a75cb2aae5f7754247dd5ae163eeae6c4 --- /dev/null +++ b/src/components/tools/drawing-manager.js @@ -0,0 +1,20 @@ +/** + * Drawing Manager + * @component drawing-manager + */ +AFRAME.registerComponent("drawing-manager", { + schema: { + }, + + init() { + this.points = []; + }, + + play() { + + }, + + pause() { + + } +} \ No newline at end of file diff --git a/src/components/tools/networked-drawing.js b/src/components/tools/networked-drawing.js new file mode 100644 index 0000000000000000000000000000000000000000..7ff84444dde720d2bd6831ef031621e012b28478 --- /dev/null +++ b/src/components/tools/networked-drawing.js @@ -0,0 +1,57 @@ +/* global THREE */ +/** + * Networked Drawing + * @component networked-drawing + */ +AFRAME.registerComponent("networked-drawing", { + schema: {}, + + init() { + this.points = []; + + var sampleClosedSpline = new THREE.CatmullRomCurve3([ + new THREE.Vector3(0, -40, -40), + new THREE.Vector3(0, 40, -40), + new THREE.Vector3(0, 140, -40), + new THREE.Vector3(0, 40, 40), + new THREE.Vector3(0, -40, 40) + ]); + + var params = { + scale: 0.02, + extrusionSegments: 100, + radiusSegments: 3, + closed: true, + animationView: false, + lookAhead: false, + cameraHelper: false + }; + + var geometry = new THREE.TubeBufferGeometry( + sampleClosedSpline, + params.extrusionSegments, + 2, + params.radiusSegments, + params.closed + ); + + var wireframeMaterial = new THREE.MeshBasicMaterial({ + color: 0x000000, + opacity: 0.3, + wireframe: true, + transparent: true + }); + var material = new THREE.MeshLambertMaterial({ color: 0xff00ff }); + var mesh = new THREE.Mesh(geometry, material); + + var wireframe = new THREE.Mesh(geometry, wireframeMaterial); + mesh.add(wireframe); + mesh.scale.set(params.scale, params.scale, params.scale); + + this.el.object3D.add(mesh); + }, + + play() {}, + + pause() {} +}); diff --git a/src/components/tools/pen.js b/src/components/tools/pen.js new file mode 100644 index 0000000000000000000000000000000000000000..52b698edd2adfc83d9fb2753272c55897e842b25 --- /dev/null +++ b/src/components/tools/pen.js @@ -0,0 +1,186 @@ +/** + * Pen tool + * @component pen + */ + +import SharedBufferGeometryManager from "../../vendor/sharedbuffergeometrymanager"; + +AFRAME.registerComponent("pen", { + schema: { + drawFrequency: { default: 1000 }, + drawPoints: { default: [] }, + minDistanceBetweenPoints: { default: 0.05 }, + segments: { default: 3 }, + radius: { default: 0.05 }, + debug: { default: true } + }, + + init() { + this.onMouseDown = this.onMouseDown.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + + let material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, color: 0xff0000 }); + + this.sharedBufferGeometryManager = new SharedBufferGeometryManager(); + this.sharedBufferGeometryManager.addSharedBuffer(0, material, THREE.TriangleStripDrawMode); + + this.isDrawing = false; + this.timeSinceLastDraw = 0; + + this.lastPosition = new THREE.Vector3(); + this.lastPositionSet = false; + this.lastSegmentsSet = false; + this.firstVertex = true; + this.lastSegments = []; + this.currentSegments = []; + for (var x = 0; x < this.data.segments; x++) { + this.lastSegments[x] = new THREE.Vector3(); + this.currentSegments[x] = new THREE.Vector3(); + } + + this.sharedBuffer = this.sharedBufferGeometryManager.getSharedBuffer(0); + this.drawing = this.sharedBuffer.getDrawing(); + let sceneEl = document.querySelector("a-scene"); + this.scene = sceneEl.object3D; + this.scene.add(this.drawing); + + if (this.data.debug) { + this.debugGeometry = new THREE.SphereGeometry(0.005, 32, 32); + this.debugMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 }); + } + }, + + play() { + document.addEventListener("mousedown", this.onMouseDown); + document.addEventListener("mouseup", this.onMouseUp); + }, + + pause() { + document.removeEventListener("mousedown", this.onMouseDown); + document.removeEventListener("mouseup", this.onMouseUp); + }, + + tick: (() => { + return function(t, dt) { + if (this.isDrawing && this.timeSinceLastDraw + dt >= this.data.drawFrequency) { + this.addPoint(this.el.object3D.position); + this.sharedBuffer.update(); + } + + this.timeSinceLastDraw = (this.timeSinceLastDraw + dt) % this.data.drawFrequency; + }; + })(), + + onMouseDown(e) { + if (e.button === 0) { + this.isDrawing = true; + this.restart(); + } + }, + + onMouseUp(e) { + if (e.button === 0) { + this.isDrawing = false; + } + }, + + restart() { + this.sharedBuffer.restartPrimitive(); + + //restart the draw (readd the last vertex) if this drawing has already been started + if (!this.firstVertex) { + this.restartDraw = true; + } + this.lastPositionSet = false; + this.lastSegmentsSet = false; + }, + + addSegments(segmentsList, point, forward, up) { + const angleIncrement = Math.PI * 2 / this.data.segments; + for (let i = 0; i < this.data.segments; i++) { + const segment = segmentsList[i]; + + this.rotatePointAroundAxis(segment, point, forward, up, angleIncrement * i, this.data.radius); + + if (this.data.debug) { + const sphere = new THREE.Mesh(this.debugGeometry, this.debugMaterial); + this.scene.add(sphere); + sphere.position.copy(segment); + } + } + }, + + addVertex(point) { + this.firstVertex = false; + this.sharedBuffer.addVertex(point.x, point.y, point.z); + }, + + addPoint: (() => { + const forward = new THREE.Vector3(); + return function(position) { + if (this.lastPositionSet) { + //don't draw if distance from last point is not far enough + const distance = position.distanceTo(this.lastPosition); + if (distance >= this.data.minDistanceBetweenPoints) { + //calculate forward only if I have lastPositionSet + forward.subVectors(position, this.lastPosition).normalize(); + + //if I don't have the lastSegments yet, add them now + if (!this.lastSegmentsSet) { + this.addSegments(this.lastSegments, this.lastPosition, forward, THREE.Object3D.DefaultUp); + this.lastSegmentsSet = true; + } + + //add currentSegments + this.addSegments(this.currentSegments, position, forward, THREE.Object3D.DefaultUp); + + //add the first vertex of the currentSegment if this is a restartDraw + if (this.restartDraw) { + this.addVertex(this.lastSegments[0]); + this.restartDraw = false; + } + + //draw the triangle strip + // this.printSegments(); + for (var j = 0; j <= this.data.segments; j++) { + this.addVertex(this.lastSegments[j % this.data.segments]); + this.addVertex(this.currentSegments[j % this.data.segments]); + } + + //copy the currentSegments to lastSegments + for (var j = 0; j < this.data.segments; j++) { + this.lastSegments[j].copy(this.currentSegments[j]); + } + + this.lastPosition.copy(position); + } + } else { + this.lastPosition.copy(position); + this.lastPositionSet = true; + } + }; + })(), + + rotatePointAroundAxis: (() => { + const calculatedDirection = new THREE.Vector3(); + return function(out, point, axis, up, angle, radius) { + calculatedDirection.copy(up); + calculatedDirection.applyAxisAngle(axis, angle); + out.copy(point).add(calculatedDirection.normalize().multiplyScalar(radius)); + }; + })(), + + printSegments() { + console.group("lastSegments"); + for (var i = 0; i < this.data.segments; i++) { + console.log(this.lastSegments[i].x, this.lastSegments[i].y, this.lastSegments[i].z); + } + console.groupEnd(); + + console.group("currentSegments"); + for (var j = 0; j < this.data.segments; j++) { + console.log(this.currentSegments[j].x, this.currentSegments[j].y, this.currentSegments[j].z); + } + console.groupEnd(); + } +}); diff --git a/src/hub.html b/src/hub.html index d54fac0939d964a1f6fe9ceca816eebb4ba8b7cc..bf421fc71224821f5e63e7f348580853463c207a 100644 --- a/src/hub.html +++ b/src/hub.html @@ -231,6 +231,7 @@ segments-height="9" segments-width="9" event-repeater="events: raycaster-intersection, raycaster-intersection-cleared; eventSource: #cursor-controller" + pen ></a-sphere> <!-- Player Rig --> @@ -370,6 +371,11 @@ nav-mesh-helper static-body="shape: none;" ></a-entity> + +<!-- <a-entity + position="0 2 0" + networked-drawing + ></a-entity> --> </a-scene> <div id="ui-root"></div> diff --git a/src/hub.js b/src/hub.js index aeb047d7a74e4325aebf1d113d4c2df866923432..4a3cf673e67524cdb3bb9ea2ab4620f63933fa56 100644 --- a/src/hub.js +++ b/src/hub.js @@ -127,6 +127,9 @@ import registerTelemetry from "./telemetry"; import { getAvailableVREntryTypes, VR_DEVICE_AVAILABILITY } from "./utils/vr-caps-detect.js"; import ConcurrentLoadDetector from "./utils/concurrent-load-detector.js"; +import "./components/tools/pen"; +// import "./components/tools/networked-drawing"; + function qsTruthy(param) { const val = qs[param]; // if the param exists but is not set (e.g. "?foo&bar"), its value is null. diff --git a/src/vendor/sharedbuffergeometry.js b/src/vendor/sharedbuffergeometry.js new file mode 100644 index 0000000000000000000000000000000000000000..343a77b078aa03dab2ae4154408dbc42fd1167bd --- /dev/null +++ b/src/vendor/sharedbuffergeometry.js @@ -0,0 +1,154 @@ +export default class SharedBufferGeometry { + + constructor(material, primitiveMode) { + this.material = material; + this.primitiveMode = primitiveMode; + + this.maxBufferSize = 1000000; + this.geometries = []; + this.current = null; + this.drawing = new THREE.Object3D(); + this.addBuffer(false); + } + + getDrawing () { + return this.drawing; + } + + restartPrimitive () { + if (this.idx.position >= this.current.attributes.position.count) { + this.addBuffer(false); + } else if (this.idx.position !== 0) { + let prev = (this.idx.position - 1) * 3; + const position = this.current.attributes.position.array; + this.addVertex(position[prev++], position[prev++], position[prev++]); + + this.idx.color++; + this.idx.normal++; + this.idx.uv++; + } + } + + remove (prevIdx, idx) { + const pos = this.current.attributes.position.array; + + // Loop through all the attributes: position, color, uv, normal,... + if (this.idx.position > idx.position) { + for (let key in this.idx) { + const componentSize = key === 'uv' ? 2 : 3; + let pos = (prevIdx[key]) * componentSize; + const start = (idx[key] + 1) * componentSize; + const end = this.idx[key] * componentSize; + for (let i = start; i < end; i++) { + this.current.attributes[key].array[pos++] = this.current.attributes[key].array[i]; + } + } + } + + for (key in this.idx) { + const diff = (idx[key] - prevIdx[key]); + this.idx[key] -= diff; + } + + this.update(); + } + + undo (prevIdx) { + this.idx = prevIdx; + this.update(); + } + + addBuffer (copyLast) { + const geometry = new THREE.BufferGeometry(); + + const vertices = new Float32Array(this.maxBufferSize * 3); + const normals = new Float32Array(this.maxBufferSize * 3); + const uvs = new Float32Array(this.maxBufferSize * 2); + const colors = new Float32Array(this.maxBufferSize * 3); + + const mesh = new THREE.Mesh(geometry, this.material); + + mesh.drawMode = this.primitiveMode; + + mesh.frustumCulled = false; + mesh.vertices = vertices; + + this.object3D = new THREE.Object3D(); + this.drawing.add(this.object3D); + this.object3D.add(mesh); + + geometry.setDrawRange(0, 0); + geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3).setDynamic(true)); + geometry.addAttribute('uv', new THREE.BufferAttribute(uvs, 2).setDynamic(true)); + geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3).setDynamic(true)); + geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3).setDynamic(true)); + + + this.previous = null; + if (this.geometries.length > 0) { + this.previous = this.current; + } + + this.idx = { + position: 0, + uv: 0, + normal: 0, + color: 0 + }; + + this.geometries.push(geometry); + this.current = geometry; + + if (this.previous && copyLast) { + let prev = (this.maxBufferSize - 2) * 3; + let col = (this.maxBufferSize - 2) * 3; + const uv = (this.maxBufferSize - 2) * 2; + let norm = (this.maxBufferSize - 2) * 3; + + const position = this.previous.attributes.position.array; + this.addVertex(position[prev++], position[prev++], position[prev++]); + this.addVertex(position[prev++], position[prev++], position[prev++]); + + const normal = this.previous.attributes.normal.array; + this.addNormal(normal[norm++], normal[norm++], normal[norm++]); + this.addNormal(normal[norm++], normal[norm++], normal[norm++]); + + const color = this.previous.attributes.color.array; + this.addColor(color[col++], color[col++], color[col++]); + this.addColor(color[col++], color[col++], color[col++]); + + const uvs = this.previous.attributes.uv.array; + + } + } + + addColor (r, g, b) { + this.current.attributes.color.setXYZ(this.idx.color++, r, g, b); + } + + addNormal (x, y, z) { + this.current.attributes.normal.setXYZ(this.idx.normal++, x, y, z); + } + + addVertex (x, y, z) { + let buffer = this.current.attributes.position; + if (this.idx.position === buffer.count) { + this.addBuffer(true); + buffer = this.current.attributes.position; + } + buffer.setXYZ(this.idx.position++, x, y, z); + } + + addUV (u, v) { + this.current.attributes.uv.setXY(this.idx.uv++, u, v); + } + + update () { + this.current.setDrawRange(0, this.idx.position); + + this.current.attributes.color.needsUpdate = true; + this.current.attributes.normal.needsUpdate = true; + this.current.attributes.position.needsUpdate = true; + this.current.attributes.uv.needsUpdate = true; + } +}; diff --git a/src/vendor/sharedbuffergeometrymanager.js b/src/vendor/sharedbuffergeometrymanager.js new file mode 100644 index 0000000000000000000000000000000000000000..555906ac85a2f5c980e141a61d11e49aeb98a1dc --- /dev/null +++ b/src/vendor/sharedbuffergeometrymanager.js @@ -0,0 +1,16 @@ +import SharedBufferGeometry from "./sharedbuffergeometry"; + +export default class SharedBufferGeometryManager { + constructor() { + this.sharedBuffers = {}; + } + + addSharedBuffer(name, material, primitiveMode) { + this.sharedBuffers[name] = new SharedBufferGeometry(material, primitiveMode); + } + + getSharedBuffer(name) { + return this.sharedBuffers[name]; + } +} +