Skip to content

Instantly share code, notes, and snippets.

@humbletim
Last active August 21, 2016 09:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save humbletim/251e7b875a3244d13bf79ac4dbb0d887 to your computer and use it in GitHub Desktop.
Save humbletim/251e7b875a3244d13bf79ac4dbb0d887 to your computer and use it in GitHub Desktop.
SnapshotRezzer

just fooling around with Snapshots + QML + Model Entities

//
// snapshot-to-entity (proof-of-concept)
// 2016.08.20 humbletim
//
// ... opens a QML Window dialog that listeners for screenshots
// and offers to upload the latest one via ATP and generate an in-world
// photoframe from it
var modelURL = 'http://cdn.xoigo.com/hifi/pod_frame.fbx#20160712';
var qmlURL = Script.resolvePath('').replace('.js','.qml');
// note: only useful during file: dev (forces .qml to be reloaded with the .js)
var cache_buster = /^file:/.test(qmlURL) ? '#'+new Date().getTime().toString(36) : '';
var window = new OverlayWindow({
source: qmlURL + cache_buster,
width: 480,
height: 320,
title: 'SnapshotRezzer v0.0.1'
});
window.fromQml.connect(function(event) {
if (event.type === 'addFrame')
addFrame(event);
else if (event.type === 'close') {
window.close();
Script.stop();
}
});
// helper to wait on cache states when needed and trigger a callback
function ready(hint, thing, onready) {
print(hint, thing, thing.state === Resource.State.FINISHED ? 'READY' : thing.state);
if (thing.state === Resource.State.FINISHED)
return onready(thing);
thing.stateChanged.connect(function wait() {
if (thing.state === Resource.State.FINISHED) {
thing.stateChanged.disconnect(wait);
onready(thing);
}
});
}
function addFrame(event) {
ready('caching:'+event.src, TextureCache.prefetch(event.src), function(texture) {
ready('caching:'+modelURL, ModelCache.prefetch(modelURL), function(model) {
print('addFrame', JSON.stringify(event,0,2));
var Origin = MyAvatar;
var frame = Entities.addEntity({
type: 'Model',
name: event.name || 'SnapshotShare',
dimensions: Vec3.multiply(.1, Vec3.ONE),
position: Vec3.sum(Vec3.multiply(.5,Quat.getUp(Origin.orientation)),Vec3.sum(Origin.position, Vec3.multiply(Quat.getFront(Origin.orientation), 3))),
rotation: MyAvatar.orientation,
dynamic: 1,
modelURL: modelURL,
compoundShapeURL: modelURL,
shapeType: 'compound',
collisionsWillMove: true,
collisionless: false,
density: 10000,
restitution: .75,
friction: .8,
//registrationPoint: Vec3.sum(Vec3.multiply(Vec3.UP,-.125/2), Vec3.HALF),
registrationPoint: Vec3.multiply(.5,Vec3.sum(Vec3.multiply(Vec3.UP,-.5), Vec3.ONE)),
textures: JSON.stringify({ backlight: event.src }),
userData: JSON.stringify({ location: Window.location.href, hash: event.hash })
});
// workaround: .naturalDimensions isn't always available the first time ModelCache reports FINISHED state
ready('recaching:'+modelURL, ModelCache.prefetch(modelURL), function(model) {
// workaround: physics, etc. appears to disrupt assignment of certain properties when first added
// so I wait a moment and then calculate/assign the final dimensions
Script.setTimeout(function() {
var props = Entities.getEntityProperties(frame);
// ~~ in theory this makes it plenty big enough, but not too overwhelming (from user's perspective)
var height = HMD.eyeHeight || 1.0;
var dims = Vec3.multiply(props.naturalDimensions, height / props.naturalDimensions.y);
Entities.editEntity(frame, {
dimensions: dims,
velocity: Vec3.multiply(0.1, Vec3.UP),
gravity: Vec3.multiply(Vec3.UP, -9.8)
});
print('//addFrame', frame, JSON.stringify(Entities.getEntityProperties(frame, ['userData','textures','name','orientation']),0,2));
// re-orient on mouse click (applies only to the local Interface client and until end of session/script)
function reorient(uuid, evt) {
print('reorienting', frame, JSON.stringify(Quat.safeEulerAngles(MyAvatar.orientation)));
Entities.editEntity(frame, { rotation: MyAvatar.orientation });
}
Script.addEventHandler(frame, 'clickDownOnEntity', reorient);
Script.scriptEnding.connect(function() { Script.removeEventHandler(frame, 'clickDownOnEntity', reorient); });
// notify our OverlayWindow QML that we were succsesful
window.sendToQml({ type: 'frameAdded', uuid: frame });
}, 250);
});
});
});
}//addFrame
//
// snapshot-to-entity (proof-of-concept)
// 2016.08.20 humbletim
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
FocusScope {
id: root
objectName: 'root'
anchors.fill: parent
focus: true
property var filename: ''
property var mapping: ''
signal sendToScript(var message)
function fromScript(event) {
console.info('fromScript', JSON.stringify(event));
if (event.type === 'frameAdded' && event.uuid !== Uuid.toString(0)) {
//sendToScript({ type: 'close' });
show(false);
button.text = button.placeholderText
bg.source = ''
enabled = true
}
}
property var window: null
Binding { target: root; property: 'window'; value: parent.parent; when: Boolean(parent && parent.parent) }
onWindowChanged: window && window.forceActiveFocus()
Image {
id: bg
z:0
focus: true
anchors.fill: parent
visible: Boolean(source)
source: ''
}
property var error: ''
onErrorChanged: {
if (error) {
_debug.text = _debug.text + '<font color=red>' + error + '</font><br />';
error = '';
}
}
function log(x) { _debug.text = _debug.text + x + '' }
Rectangle {
z: 0;
anchors.top: parent.top
width: parent.width
height: _debug.height
color: 'gray'
opacity: .9
TextEdit {
id: _debug
z: 0; color: 'white'
anchors.margins: 0
text: '<b></b>';
textFormat: TextEdit.RichText
readOnly: true
wrapMode: Text.WordWrap;
selectByMouse: true; selectByKeyboard: true; font.pixelSize: 12;
verticalAlignment: Text.AlignTop
horizontalAlignment: Text.AlignLeft
}
}
Button {
z:1
id: button
height: parent.height/3
width: parent.width
anchors.bottom: parent.bottom
opacity: .8
visible: true
enabled: true
focus: true
property var placeholderText: '(snapshot detection enabled)\nyou can hit (x) or ESC to hide this dialog'
property var permsText: 'click to generate a photo frame\n(note: requires canWriteToAssetServer && canRez*Entities)'
text: placeholderText
onClicked: {
console.info('click!', filename, mapping, (bg.source+'').substr(0,32));
if (enabled && filename && mapping) {
enabled = false; // prevent fallthru clicking bug
_debug.text = 'Assets.uploadFile...';
Assets.uploadFile(filename, mapping, function started() {
log('started...')
console.info('started', mapping);
}, function completed(err, url) {
console.info('completed', mapping, err, url);
if (err) {
error += err;
window.forceActiveFocus()
return enabled = true;
} else
log('completed... ' + url);
// verify it actually worked (by resolving the mapping into its ATP hash)
Assets.getMapping(mapping, function(err, hash) {
console.info('gotMapping', mapping, err, hash);
if (err) {
error += 'getMapping error: ' + err;
return enabled = true
}
bg.source = 'atp:'+mapping;
log('\nDONE: ' + bg.source)
sendToScript({
type: 'addFrame',
src: bg.source,
hash: hash,
mapping: mapping,
path: filename
});
});
}, false);
}
}//button.onClicked
// MouseArea {
// id: mouseArea
// anchors.fill: parent
// hoverEnabled: true
// }
// style: ButtonStyle {
// background: Rectangle {
// color: mouseArea.containsMouse ? '#fefefe' : '#efefef'
// border.color: enabled ? 'lime' : 'white'
// radius: 8
// }
// label: Component {
// Text {
// focus: true
// text: button.text
// wrapMode: Text.WordWrap
// verticalAlignment: Text.AlignTop
// horizontalAlignment: Text.AlignLeft
// anchors.fill: parent
// font.pixelSize: 11
// }
// }
// }
}
Keys.onPressed: {
console.info('onPressed', event);
if (!visible)
return;
switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
event.accepted = true
show(false);
break
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true
button.enabled && button.clicked(event)
break
}
}
Connections {
target: window
onVisibleChanged: if (!visible && !mapping) button.text = button.placeholderText
onFocusChanged: { console.info('focus', window.focus); if (!focus && button.enabled) show(false) }
}
function show(b) { window.shown = b; }
Connections {
target: Window
onSnapshotTaken: {
console.info('SNAPSHOT DETECTED', path, button.text);
filename = path;
bg.source = 'file://'+filename;
mapping = '/snapshots/'+filename.split(/[\\/]/).pop().split(/[#?]/)[0];
show(true);
_debug.text = ''
log('SNAPSHOT detected');
log('<pre>current perms: '+JSON.stringify({
canRez: Entities.canRez(),
canRezTmp: Entities.canRezTmp(),
canWriteToAssetServer: '(permissions check unavailable)'
},0,2).trim().replace(/\n/g,'<br />')+'</pre>');
log('image: '+filename);
log('mapping: '+mapping);
button.enabled = true
button.text = button.permsText;
button.forceActiveFocus()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment