├── .gitignore
├── .gitmodules
├── README.md
├── dist
├── aframe.depthkit.js
└── aframe.depthkit.min.js
├── docs
├── movement.gif
└── screenshot.png
├── examples
└── simple.html
├── package-lock.json
├── package.json
└── src
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "ext/DepthKit"]
2 | path = ext/DepthKit
3 | url = git@github.com:juniorxsound/DepthKit.js.git
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DepthKit for A-Frame
2 | [](http://makeapullrequest.com)
3 |
4 | An A-Frame component for rendering [DepthKit](http://www.depthkit.tv) volumetric videos in WebVR. The A-Frame component wraps [DepthKit.js](https://github.com/juniorxsound/DepthKit.js) which is a small library that provides the same functionality for [Three.js](https://github.com/mrdoob/three.js) projects.
5 | - [Usage](#usage)
6 | - [Contribute](#contribute)
7 |
8 | 
9 |
10 | ## Usage
11 | Start by cloning/forking the repository and including ```aframe.depthkit.min.js``` from the ```./dist``` folder (make sure to clone using ```--recursive``` flag if you plan to run the examples to clone the git submodules too)
12 |
13 | The simplest way you can initialize a DepthKit video is to create a ```depthkit``` entity inside an A-Frame ```a-scene``` tag:
14 | ```html
15 |
16 |
21 |
22 |
23 | ```
24 | Where the ```type``` attribute support ```wire/points/mesh``` for rendering different styles, ```metaPath``` is the path to the ```.txt``` file with the parameters (exported by DepthKit Visualize) and ```videoPath``` is for the path to the video file.
25 |
26 | ## Contribute
27 | PRs are welcome ✊🏻 make sure to clone using the ```git clone --recursive```.
28 |
29 | ### Build system
30 | - ```npm run start``` uses ```concurrently``` to start both an ```http-server``` and a ```watchify``` build opreation on every save to ```./dist/aframe.depthkit.js```
31 | - ```npm run build``` builds and minifies to ```./dist/aframe.depthkit.min.js```
32 |
--------------------------------------------------------------------------------
/dist/aframe.depthkit.js:
--------------------------------------------------------------------------------
1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o _DepthSaturationThreshhold && depthsamplehsv.b > _DepthBrightnessThreshold ? depthsamplehsv.r : 0.0;\n}\n\nvoid main() {\n vec4 texSize = vec4(1.0 / width, 1.0 / height, width, height);\n\n vec2 centerpix = texSize.xy * .5;\n vec2 textureStep = 1.0 / meshDensity;\n vec2 basetex = floor(position.xy * textureStep * texSize.zw) * texSize.xy;\n vec2 imageCoordinates = crop.xy + (basetex * crop.zw);\n basetex.y = 1.0 - basetex.y;\n\n vec2 depthTexCoord = basetex * vec2(1.0, 0.5) + centerpix;\n vec2 colorTexCoord = basetex * vec2(1.0, 0.5) + vec2(0.0, 0.5) + centerpix;\n\n vUv = colorTexCoord;\n vPos = (modelMatrix * vec4(position, 1.0 )).xyz;\n vNormal = normalMatrix * normal;\n\n //check neighbors\n //texture coords come in as [0.0 - 1.0] for this whole plane\n float depth = depthForPoint(depthTexCoord);\n\n float neighborDepths[8];\n neighborDepths[0] = depthForPoint(depthTexCoord + vec2(0.0, textureStep.y));\n neighborDepths[1] = depthForPoint(depthTexCoord + vec2(textureStep.x, 0.0));\n neighborDepths[2] = depthForPoint(depthTexCoord + vec2(0.0, -textureStep.y));\n neighborDepths[3] = depthForPoint(depthTexCoord + vec2(-textureStep.x, 0.0));\n neighborDepths[4] = depthForPoint(depthTexCoord + vec2(-textureStep.x, -textureStep.y));\n neighborDepths[5] = depthForPoint(depthTexCoord + vec2(textureStep.x, textureStep.y));\n neighborDepths[6] = depthForPoint(depthTexCoord + vec2(textureStep.x, -textureStep.y));\n neighborDepths[7] = depthForPoint(depthTexCoord + vec2(-textureStep.x, textureStep.y));\n\n visibility = 1.0;\n int numDudNeighbors = 0;\n //search neighbor verts in order to see if we are near an edge\n //if so, clamp to the surface closest to us\n if (depth < _Epsilon || (1.0 - depth) < _Epsilon)\n {\n // float depthDif = 1.0;\n float nearestDepth = 1.0;\n for (int i = 0; i < 8; i++)\n {\n float depthNeighbor = neighborDepths[i];\n if (depthNeighbor >= _Epsilon && (1.0 - depthNeighbor) > _Epsilon)\n {\n // float thisDif = abs(nearestDepth - depthNeighbor);\n if (depthNeighbor < nearestDepth)\n {\n // depthDif = thisDif;\n nearestDepth = depthNeighbor;\n }\n }\n else\n {\n numDudNeighbors++;\n }\n }\n\n depth = nearestDepth;\n visibility = 0.8;\n\n // blob filter\n if (numDudNeighbors > 6)\n {\n visibility = 0.0;\n }\n }\n\n // internal edge filter\n float maxDisparity = 0.0;\n for (int i = 0; i < 8; i++)\n {\n float depthNeighbor = neighborDepths[i];\n if (depthNeighbor >= _Epsilon && (1.0 - depthNeighbor) > _Epsilon)\n {\n maxDisparity = max(maxDisparity, abs(depth - depthNeighbor));\n }\n }\n visibility *= 1.0 - maxDisparity;\n\n float z = depth * (maxdepth - mindepth) + mindepth;\n vec4 worldPos = extrinsics * vec4((imageCoordinates * imageDimensions - principalPoint) * z / focalLength, z, 1.0);\n worldPos.w = 1.0;\n\n gl_Position = projectionMatrix * modelViewMatrix * worldPos;\n}"]);switch (this.video = document.createElement("video"), this.video.id = "depthkit-video", this.video.crossOrigin = "anonymous", this.video.setAttribute("crossorigin", "anonymous"), this.video.setAttribute("webkit-playsinline", "webkit-playsinline"), this.video.setAttribute("playsinline", "playsinline"), this.video.src = i, this.video.autoplay = !1, this.video.loop = !1, this.video.load(), this.videoTexture = new THREE.VideoTexture(this.video), this.videoTexture.minFilter = THREE.NearestFilter, this.videoTexture.magFilter = THREE.LinearFilter, this.videoTexture.format = THREE.RGBFormat, this.videoTexture.generateMipmaps = !1, this.manager = new THREE.LoadingManager(), this.props, s.geo || s.buildGeomtery(), this.material = new THREE.ShaderMaterial({ uniforms: { map: { type: "t", value: this.videoTexture }, time: { type: "f", value: 0 }, mindepth: { type: "f", value: 0 }, maxdepth: { type: "f", value: 0 }, meshDensity: { value: new THREE.Vector2(l, h) }, focalLength: { value: new THREE.Vector2(1, 1) }, principalPoint: { value: new THREE.Vector2(1, 1) }, imageDimensions: { value: new THREE.Vector2(512, 828) }, extrinsics: { value: new THREE.Matrix4() }, crop: { value: new THREE.Vector4(0, 0, 1, 1) }, width: { type: "f", value: 0 }, height: { type: "f", value: 0 }, opacity: { type: "f", value: 1 }, isPoints: { type: "b", value: !1 }, pointSize: { type: "f", value: 3 } }, vertexShader: r, fragmentShader: n, transparent: !0 }), this.material.side = THREE.DoubleSide, e) {case "wire":
47 | this.material.wireframe = !0, this.mesh = new THREE.Mesh(s.geo, this.material);break;case "points":
48 | this.material.uniforms.isPoints.value = !0, this.mesh = new THREE.Points(s.geo, this.material);break;default:
49 | this.mesh = new THREE.Mesh(s.geo, this.material);}return this.jsonLoader = new THREE.FileLoader(this.manager), this.jsonLoader.setResponseType("json"), this.jsonLoader.load(t, function (e) {
50 | o.props = e, o.material.uniforms.width.value = o.props.textureWidth, o.material.uniforms.height.value = o.props.textureHeight, o.material.uniforms.mindepth.value = o.props.nearClip, o.material.uniforms.maxdepth.value = o.props.farClip, o.material.uniforms.focalLength.value = o.props.depthFocalLength, o.material.uniforms.principalPoint.value = o.props.depthPrincipalPoint, o.material.uniforms.imageDimensions.value = o.props.depthImageSize, o.material.uniforms.crop.value = o.props.crop;var t = o.props.extrinsics;o.material.uniforms.extrinsics.value.set(t.e00, t.e10, t.e20, t.e30, t.e01, t.e11, t.e21, t.e31, t.e02, t.e12, t.e22, t.e32, t.e03, t.e13, t.e23, t.e33);var i = new THREE.BoxGeometry(o.props.boundsSize.x, o.props.boundsSize.y, o.props.boundsSize.z),
51 | n = new THREE.MeshBasicMaterial({ color: 16776960, wireframe: !0 });o.collider = new THREE.Mesh(i, n), o.collider.visible = !1, o.mesh.add(o.collider), THREE.SceneUtils.detach(o.collider, o.mesh, o.mesh.parent), o.collider.position.set(0, 1, 0);
52 | }), this.mesh.frustumCulled = !1, (this.mesh.depthkit = this).mesh.name = "depthkit", this.mesh;
53 | }return n(s, [{ key: "setPointSize", value: function value(e) {
54 | this.material.uniforms.isPoints.value ? this.material.uniforms.pointSize.value = e : console.warn("Can not set point size because the current character is not set to render points");
55 | } }, { key: "setOpacity", value: function value(e) {
56 | this.material.uniforms.opacity.value = e;
57 | } }, { key: "setLineWidth", value: function value(e) {
58 | this.material.wireframe ? this.material.wireframeLinewidth = e : console.warn("Can not set the line width because the current character is not set to render wireframe");
59 | } }, { key: "play", value: function value() {
60 | this.video.isPlaying ? console.warn("Can not play because the character is already playing") : this.video.play();
61 | } }, { key: "stop", value: function value() {
62 | this.video.currentTime = 0, this.video.pause();
63 | } }, { key: "pause", value: function value() {
64 | this.video.pause();
65 | } }, { key: "setLoop", value: function value(e) {
66 | this.video.loop = e;
67 | } }, { key: "setVolume", value: function value(e) {
68 | this.video.volume = e;
69 | } }, { key: "update", value: function value(e) {
70 | this.material.uniforms.time.value = e;
71 | } }, { key: "toggleColliderVisiblity", value: function value() {
72 | this.mesh.collider.visible = !this.mesh.collider.visible;
73 | } }, { key: "dispose", value: function value() {
74 | try {
75 | this.mesh.parent.remove(this.mesh);
76 | } catch (e) {
77 | console.warn(e);
78 | } finally {
79 | this.mesh.traverse(function (e) {
80 | void 0 !== e.geometry && (e.geometry.dispose(), e.material.dispose());
81 | });
82 | }
83 | } }], [{ key: "buildGeomtery", value: function value() {
84 | s.geo = new THREE.Geometry();for (var e = 0; e < h; e++) {
85 | for (var t = 0; t < l; t++) {
86 | s.geo.vertices.push(new THREE.Vector3(t, e, 0));
87 | }
88 | }for (var i = 0; i < h - 1; i++) {
89 | for (var n = 0; n < l - 1; n++) {
90 | s.geo.faces.push(new THREE.Face3(n + i * l, n + (i + 1) * l, n + 1 + i * l)), s.geo.faces.push(new THREE.Face3(n + 1 + i * l, n + (i + 1) * l, n + 1 + (i + 1) * l));
91 | }
92 | }
93 | } }]), s;
94 | }();i.default = o;
95 | }, { glslify: 1 }], 3: [function (e, t, i) {
96 | "use strict";
97 | Object.defineProperty(i, "__esModule", { value: !0 }), i.DepthKit = void 0;var n,
98 | o = "function" == typeof Symbol && "symbol" == _typeof(Symbol.iterator) ? function (e) {
99 | return typeof e === "undefined" ? "undefined" : _typeof(e);
100 | } : function (e) {
101 | return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e === "undefined" ? "undefined" : _typeof(e);
102 | },
103 | r = e("./depthkit"),
104 | s = (n = r) && n.__esModule ? n : { default: n };"undefined" != typeof window && "object" === o(window.THREE) ? window.DepthKit = s.default : console.warn("[DepthKit.js] It seems like THREE is not included in your code, try including it before DepthKit.js"), i.DepthKit = s.default;
105 | }, { "./depthkit": 2 }] }, {}, [3]);
106 |
107 | //Make sure AFrame is available
108 | if (typeof AFRAME === 'undefined') {
109 | throw new Error('Component attempted to register before AFRAME was available.');
110 | }
111 |
112 | //AFrame DepthKit.js wrapper entity
113 | AFRAME.registerComponent('depthkit', {
114 |
115 | schema: {
116 | type: { type: 'string', default: 'mesh' },
117 | videoPath: { type: 'string' },
118 | metaPath: { type: 'string' },
119 | loop: { type: 'boolean', default: true },
120 | autoplay: { type: 'boolean', default: true }
121 | },
122 |
123 | /**
124 | * Set if component needs multiple instancing.
125 | */
126 | multiple: true,
127 |
128 | /**
129 | * Called once when component is attached. Generally for initial setup.
130 | */
131 | init: function init() {
132 |
133 | //Create a depthkit instance
134 | var character = new DepthKit(this.data.type, this.data.metaPath, this.data.videoPath);
135 | //Will it loop?
136 | character.depthkit.setLoop(this.data.loop);
137 |
138 | //Rotate it back to position
139 | character.rotation.z = THREE.Math.degToRad(90);
140 |
141 | //If autoplay is on play the take
142 | if (this.data.autoplay) character.depthkit.play();
143 |
144 | //Set the Object3D
145 | this.el.setObject3D('mesh', character);
146 |
147 | //Translate it so it is in front of you at eye level
148 | this.el.object3D.scale.multiplyScalar(0.001);
149 | this.el.object3D.position.z = -2;
150 | this.el.object3D.position.y = 1;
151 | },
152 |
153 | /**
154 | * Called when component is attached and when component data changes.
155 | * Generally modifies the entity based on the data.
156 | */
157 | update: function update(oldData) {},
158 |
159 | /**
160 | * Called when a component is removed (e.g., via removeAttribute).
161 | * Generally undoes all modifications to the entity.
162 | */
163 | remove: function remove() {},
164 |
165 | /**
166 | * Called on each scene tick.
167 | */
168 | // tick: function (t) { },
169 |
170 | /**
171 | * Called when entity pauses.
172 | * Use to stop or remove any dynamic or background behavior such as events.
173 | */
174 | pause: function pause() {},
175 |
176 | /**
177 | * Called when entity resumes.
178 | * Use to continue or add any dynamic or background behavior such as events.
179 | */
180 | play: function play() {}
181 | });
182 |
183 | },{}]},{},[1]);
184 |
--------------------------------------------------------------------------------
/dist/aframe.depthkit.min.js:
--------------------------------------------------------------------------------
1 | !function e(t,i,n){function o(a,s){if(!i[a]){if(!t[a]){var l="function"==typeof require&&require;if(!s&&l)return l(a,!0);if(r)return r(a,!0);var p=new Error("Cannot find module '"+a+"'");throw p.code="MODULE_NOT_FOUND",p}var h=i[a]={exports:{}};t[a][0].call(h.exports,function(e){var i=t[a][1][e];return o(i||e)},h,h.exports,e,t,i,n)}return i[a].exports}for(var r="function"==typeof require&&require,a=0;a _DepthSaturationThreshhold && depthsamplehsv.b > _DepthBrightnessThreshold ? depthsamplehsv.r : 0.0;\n}\n\nvoid main() {\n vec4 texSize = vec4(1.0 / width, 1.0 / height, width, height);\n\n vec2 centerpix = texSize.xy * .5;\n vec2 textureStep = 1.0 / meshDensity;\n vec2 basetex = floor(position.xy * textureStep * texSize.zw) * texSize.xy;\n vec2 imageCoordinates = crop.xy + (basetex * crop.zw);\n basetex.y = 1.0 - basetex.y;\n\n vec2 depthTexCoord = basetex * vec2(1.0, 0.5) + centerpix;\n vec2 colorTexCoord = basetex * vec2(1.0, 0.5) + vec2(0.0, 0.5) + centerpix;\n\n vUv = colorTexCoord;\n vPos = (modelMatrix * vec4(position, 1.0 )).xyz;\n vNormal = normalMatrix * normal;\n\n //check neighbors\n //texture coords come in as [0.0 - 1.0] for this whole plane\n float depth = depthForPoint(depthTexCoord);\n\n float neighborDepths[8];\n neighborDepths[0] = depthForPoint(depthTexCoord + vec2(0.0, textureStep.y));\n neighborDepths[1] = depthForPoint(depthTexCoord + vec2(textureStep.x, 0.0));\n neighborDepths[2] = depthForPoint(depthTexCoord + vec2(0.0, -textureStep.y));\n neighborDepths[3] = depthForPoint(depthTexCoord + vec2(-textureStep.x, 0.0));\n neighborDepths[4] = depthForPoint(depthTexCoord + vec2(-textureStep.x, -textureStep.y));\n neighborDepths[5] = depthForPoint(depthTexCoord + vec2(textureStep.x, textureStep.y));\n neighborDepths[6] = depthForPoint(depthTexCoord + vec2(textureStep.x, -textureStep.y));\n neighborDepths[7] = depthForPoint(depthTexCoord + vec2(-textureStep.x, textureStep.y));\n\n visibility = 1.0;\n int numDudNeighbors = 0;\n //search neighbor verts in order to see if we are near an edge\n //if so, clamp to the surface closest to us\n if (depth < _Epsilon || (1.0 - depth) < _Epsilon)\n {\n // float depthDif = 1.0;\n float nearestDepth = 1.0;\n for (int i = 0; i < 8; i++)\n {\n float depthNeighbor = neighborDepths[i];\n if (depthNeighbor >= _Epsilon && (1.0 - depthNeighbor) > _Epsilon)\n {\n // float thisDif = abs(nearestDepth - depthNeighbor);\n if (depthNeighbor < nearestDepth)\n {\n // depthDif = thisDif;\n nearestDepth = depthNeighbor;\n }\n }\n else\n {\n numDudNeighbors++;\n }\n }\n\n depth = nearestDepth;\n visibility = 0.8;\n\n // blob filter\n if (numDudNeighbors > 6)\n {\n visibility = 0.0;\n }\n }\n\n // internal edge filter\n float maxDisparity = 0.0;\n for (int i = 0; i < 8; i++)\n {\n float depthNeighbor = neighborDepths[i];\n if (depthNeighbor >= _Epsilon && (1.0 - depthNeighbor) > _Epsilon)\n {\n maxDisparity = max(maxDisparity, abs(depth - depthNeighbor));\n }\n }\n visibility *= 1.0 - maxDisparity;\n\n float z = depth * (maxdepth - mindepth) + mindepth;\n vec4 worldPos = extrinsics * vec4((imageCoordinates * imageDimensions - principalPoint) * z / focalLength, z, 1.0);\n worldPos.w = 1.0;\n\n gl_Position = projectionMatrix * modelViewMatrix * worldPos;\n}"]);switch(this.video=document.createElement("video"),this.video.id="depthkit-video",this.video.crossOrigin="anonymous",this.video.setAttribute("crossorigin","anonymous"),this.video.setAttribute("webkit-playsinline","webkit-playsinline"),this.video.setAttribute("playsinline","playsinline"),this.video.src=s,this.video.autoplay=!1,this.video.loop=!1,this.video.load(),this.videoTexture=new THREE.VideoTexture(this.video),this.videoTexture.minFilter=THREE.NearestFilter,this.videoTexture.magFilter=THREE.LinearFilter,this.videoTexture.format=THREE.RGBFormat,this.videoTexture.generateMipmaps=!1,this.manager=new THREE.LoadingManager,this.props,e.geo||e.buildGeomtery(),this.material=new THREE.ShaderMaterial({uniforms:{map:{type:"t",value:this.videoTexture},time:{type:"f",value:0},mindepth:{type:"f",value:0},maxdepth:{type:"f",value:0},meshDensity:{value:new THREE.Vector2(r,a)},focalLength:{value:new THREE.Vector2(1,1)},principalPoint:{value:new THREE.Vector2(1,1)},imageDimensions:{value:new THREE.Vector2(512,828)},extrinsics:{value:new THREE.Matrix4},crop:{value:new THREE.Vector4(0,0,1,1)},width:{type:"f",value:0},height:{type:"f",value:0},opacity:{type:"f",value:1},isPoints:{type:"b",value:!1},pointSize:{type:"f",value:3}},vertexShader:p,fragmentShader:l,transparent:!0}),this.material.side=THREE.DoubleSide,t){case"wire":this.material.wireframe=!0,this.mesh=new THREE.Mesh(e.geo,this.material);break;case"points":this.material.uniforms.isPoints.value=!0,this.mesh=new THREE.Points(e.geo,this.material);break;default:this.mesh=new THREE.Mesh(e.geo,this.material)}return this.jsonLoader=new THREE.FileLoader(this.manager),this.jsonLoader.setResponseType("json"),this.jsonLoader.load(i,function(e){n.props=e,n.material.uniforms.width.value=n.props.textureWidth,n.material.uniforms.height.value=n.props.textureHeight,n.material.uniforms.mindepth.value=n.props.nearClip,n.material.uniforms.maxdepth.value=n.props.farClip,n.material.uniforms.focalLength.value=n.props.depthFocalLength,n.material.uniforms.principalPoint.value=n.props.depthPrincipalPoint,n.material.uniforms.imageDimensions.value=n.props.depthImageSize,n.material.uniforms.crop.value=n.props.crop;var t=n.props.extrinsics;n.material.uniforms.extrinsics.value.set(t.e00,t.e10,t.e20,t.e30,t.e01,t.e11,t.e21,t.e31,t.e02,t.e12,t.e22,t.e32,t.e03,t.e13,t.e23,t.e33);var i=new THREE.BoxGeometry(n.props.boundsSize.x,n.props.boundsSize.y,n.props.boundsSize.z),o=new THREE.MeshBasicMaterial({color:16776960,wireframe:!0});n.collider=new THREE.Mesh(i,o),n.collider.visible=!1,n.mesh.add(n.collider),THREE.SceneUtils.detach(n.collider,n.mesh,n.mesh.parent),n.collider.position.set(0,1,0)}),this.mesh.frustumCulled=!1,(this.mesh.depthkit=this).mesh.name="depthkit",this.mesh}return n(e,[{key:"setPointSize",value:function(e){this.material.uniforms.isPoints.value?this.material.uniforms.pointSize.value=e:console.warn("Can not set point size because the current character is not set to render points")}},{key:"setOpacity",value:function(e){this.material.uniforms.opacity.value=e}},{key:"setLineWidth",value:function(e){this.material.wireframe?this.material.wireframeLinewidth=e:console.warn("Can not set the line width because the current character is not set to render wireframe")}},{key:"play",value:function(){this.video.isPlaying?console.warn("Can not play because the character is already playing"):this.video.play()}},{key:"stop",value:function(){this.video.currentTime=0,this.video.pause()}},{key:"pause",value:function(){this.video.pause()}},{key:"setLoop",value:function(e){this.video.loop=e}},{key:"setVolume",value:function(e){this.video.volume=e}},{key:"update",value:function(e){this.material.uniforms.time.value=e}},{key:"toggleColliderVisiblity",value:function(){this.mesh.collider.visible=!this.mesh.collider.visible}},{key:"dispose",value:function(){try{this.mesh.parent.remove(this.mesh)}catch(e){console.warn(e)}finally{this.mesh.traverse(function(e){void 0!==e.geometry&&(e.geometry.dispose(),e.material.dispose())})}}}],[{key:"buildGeomtery",value:function(){e.geo=new THREE.Geometry;for(var t=0;t
2 |
3 | A-Frame DepthKit
4 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |