Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Screenshot WIP for feedback #179

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@
brush
if-no-vr-headset="visible: false"
paint-controls="hand: left"
screenshot-camera
trigger-mode="brush"
ui></a-entity>
<a-entity id="right-hand"
brush
if-no-vr-headset="visible: false"
paint-controls="hand: right"
screenshot-camera
trigger-mode="brush"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this should be called action-mode since it might support more than just the trigger button, IE using the grips for scaling instead of undo

ui></a-entity>
<a-entity id="ground"
geometry="primitive: circle; radius: 12;"
Expand Down
14 changes: 12 additions & 2 deletions src/components/brush.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ AFRAME.registerComponent('brush', {

var self = this;

this.el.addEventListener('trigger-mode-changed', this.triggerModeChanged.bind(this));
this.triggerModeChanged();

this.el.addEventListener('axismove', function (evt) {
if (evt.detail.axis[0] === 0 && evt.detail.axis[1] === 0) {
return;
Expand All @@ -37,15 +40,15 @@ AFRAME.registerComponent('brush', {
});

this.el.addEventListener('buttondown', function (evt) {
if (!self.data.enabled) { return; }
if (!self.enabled) { return; }
// Grip
if (evt.detail.id === 2) {
self.system.undo();
}
});

this.el.addEventListener('buttonchanged', function (evt) {
if (!self.data.enabled) { return; }
if (!self.enabled) { return; }
// Trigger
if (evt.detail.id === 1) {
var value = evt.detail.state.value;
Expand All @@ -65,7 +68,14 @@ AFRAME.registerComponent('brush', {
}
});
},

triggerModeChanged: function() {
this.enabled = this.data.enabled && this.el.getAttribute('trigger-mode') == 'brush'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the proper way to do this to have another component that manages the trigger-mode attribute to data.enabled binding? To make this more reusable it would be nice if this didn't have to worry about the external trigger-mode attribute

},

update: function (oldData) {
this.triggerModeChanged();

var data = this.data;
if (oldData.color !== data.color) {
this.color.set(data.color);
Expand Down
113 changes: 113 additions & 0 deletions src/components/screenshot-camera.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/* globals AFRAME THREE */
AFRAME.registerComponent('screenshot-camera', {
schema: {
width: {default: 1024, min: 1, max: 2048},
height: {default: 768, min: 1, max: 1516},
},
init: function () {
var data = this.data;
var width = data.width;
var height = data.height;
this.sceneEl = document.querySelector('a-scene');
this.scene = this.sceneEl.object3D

this.renderTarget = new THREE.WebGLRenderTarget(width, height, {
format: THREE.RGBAFormat,
type: THREE.UnsignedByteType,
})

var ratio = width / height;
this.camera = new THREE.PerspectiveCamera(55, ratio, 0.01, 1000 );
this.camera.rotation.x = Math.PI / 2
this.camera.rotation.y = Math.PI
this.camera.rotation.z = Math.PI
this.screen = new THREE.Mesh(
new THREE.PlaneBufferGeometry(ratio * 0.2, 0.2),
new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
map: this.renderTarget
})
)
this.screen.rotation.set(3 * Math.PI / 2, 0, 0)
this.screen.position.set(0.2, 0, -0.2)
this.el.object3D.add(this.screen)
this.el.object3D.add(this.camera)

this.el.addEventListener('trigger-mode-changed', this.triggerModeChanged.bind(this))
this.triggerModeChanged();
this.canTakePicture = true
this.el.addEventListener('triggerchanged', function (evt) {
if (!this.enabled) {
return
}

if (evt.detail.value == 1 && this.canTakePicture) {
this.saveNextTick = true
this.canTakePicture = false
} else if (evt.detail.value < 1) {
this.canTakePicture = true
}
}.bind(this));

this.canvas = document.createElement('canvas')
this.canvas.width = width
this.canvas.height = height
},

triggerModeChanged: function() {
this.enabled = this.el.getAttribute('trigger-mode') == 'camera'
this.screen.visible = this.enabled
},

tick: function(time, timeDelta) {
if (!this.enabled) return
this.sceneEl.renderer.render(this.scene, this.camera, this.renderTarget, true)
if (this.saveNextTick) {
this.saveCapture();
this.saveNextTick = false;
}
},

saveCapture: function () {
var width = this.data.width
var height = this.data.height
this.pixels = new Uint8Array(4 * width * height)
var renderer = this.sceneEl.renderer

var _gl = renderer.context
var framebuffer = renderer.properties.get(this.renderTarget).__webglFramebuffer
_gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
_gl.readPixels( 0, 0, width, height, _gl.RGBA,_gl.UNSIGNED_BYTE, this.pixels );
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't get WebGLRenderer.readRenderTargetPixels to work here, unsure why as this looks like the same code. Is there a good way to debug Threejs while working on a-painter?

this.pixels = this.flipPixelsVertically(this.pixels, width, height)

this.imageData = new ImageData(new Uint8ClampedArray(this.pixels), width, height)
this.canvas.getContext('2d').putImageData(this.imageData, 0, 0)
this.canvas.toBlob(function (blob) {
var url = URL.createObjectURL(blob);
var fileName = 'screenshot-' + document.title + '-' + Date.now() + '.png';
var aEl = document.createElement('a');
aEl.href = url;
aEl.setAttribute('download', fileName);
aEl.innerHTML = 'downloading...';
aEl.style.display = 'none';
document.body.appendChild(aEl);
setTimeout(function () {
aEl.click();
document.body.removeChild(aEl);
}, 1);
}, 'image/png');
},

flipPixelsVertically: function (pixels, width, height) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This flipping seems to cause frame skipping. It actually kind of works because the white flash feels like taking a flash photograph, but it should probably be moved into a web worker or something.

var flippedPixels = pixels.slice(0);
for (var x = 0; x < width; ++x) {
for (var y = 0; y < height; ++y) {
flippedPixels[x * 4 + y * width * 4] = pixels[x * 4 + (height - y) * width * 4];
flippedPixels[x * 4 + 1 + y * width * 4] = pixels[x * 4 + 1 + (height - y) * width * 4];
flippedPixels[x * 4 + 2 + y * width * 4] = pixels[x * 4 + 2 + (height - y) * width * 4];
flippedPixels[x * 4 + 3 + y * width * 4] = pixels[x * 4 + 3 + (height - y) * width * 4];
}
}
return flippedPixels;
},
});
7 changes: 7 additions & 0 deletions src/components/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ AFRAME.registerComponent('ui', {
}
case name === 'clear': {
if (!this.pressedObjects[name]) {
this.toggleCaptureCamera();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to get another button in the menu for this. If people like the change maybe @feiss can hook up a camera button.

this.el.sceneEl.systems.brush.clear();
}
break;
Expand Down Expand Up @@ -355,6 +356,12 @@ AFRAME.registerComponent('ui', {
this.handEl.setAttribute('brush', 'size', brushSize);
},

toggleCaptureCamera: function() {
var enableCamera = this.handEl.getAttribute('trigger-mode') != 'camera'
this.handEl.setAttribute('trigger-mode', enableCamera ? 'camera' : 'brush')
this.handEl.emit('trigger-mode-changed')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a built-in way to listen for any attribute changes, or do i need to emit custom events each time i change?

},

handleHover: function () {
this.updateHoverObjects();
this.updateMaterials();
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require('./components/line.js');
require('./components/look-controls-alt.js');
require('./components/orbit-controls.js');
require('./components/paint-controls.js');
require('./components/screenshot-camera.js');
require('./components/ui.js');
require('./components/ui-raycaster.js');

Expand Down