just fooling around with Snapshots + QML + Model Entities
-
-
Save humbletim/251e7b875a3244d13bf79ac4dbb0d887 to your computer and use it in GitHub Desktop.
SnapshotRezzer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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