Implementing Three.js SSAOPass in AFrame - html

I was able to successfully integrate Threejs Effect composer in aframe as a component by exporting everything as THREE.Effectcomposer, THREE.SSAOPass etc. and adding the effect inside a aframe component and i tweaked the AFrame renderer to update the effects in the scene. OutlinePass from threejs worked fine in this code but SSAO is not working and i don't get any errors. Please someone help me figure out the problem. the code for SSAOPass looks like this
AFRAME.registerComponent('ssao', {
init: function () {
this.el.addEventListener('that', evt => this.onEnter());
this.el.addEventListener('mouseleave', evt => this.onLeave());
setTimeout(() => this.el.emit("that"), 2000);
},
onEnter: function () {
const scene = this.el.sceneEl.object3D;
const camera = this.el.sceneEl.camera;
const renderer = this.el.sceneEl.renderer;
const render = renderer.render;
const composer = new THREE.EffectComposer(renderer);
//let renderPass = new THREE.RenderPass(scene, camera);
//let outlinePass = new THREE.OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera);
const ssaoPass = new THREE.SSAOPass( scene, camera, window.innerWidth, window.innerHeight );
//composer.addPass(renderPass);
//composer.addPass(outlinePass);
ssaoPass.kernelRadius = 16;
composer.addPass( ssaoPass );
// let objects = [];
// this.el.object3D.traverse(node => {
// if (!node.isMesh) return;
// objects.push(node);
// });
// outlinePass.selectedObjects = objects;
// outlinePass.renderToScreen = true;
// outlinePass.edgeStrength = this.data.strength;
// outlinePass.edgeGlow = this.data.glow;
// outlinePass.visibleEdgeColor.set(this.data.color);
// HACK the AFRAME render method (a bit ugly)
const clock = new THREE.Clock();
this.originalRenderMethod = render;
let calledByComposer = false;
renderer.render = function () {
if (calledByComposer) {
render.apply(renderer, arguments);
} else {
calledByComposer = true;
composer.render(clock.getDelta());
calledByComposer = false;
}
};
},
onLeave: function () {
this.el.sceneEl.renderer.render = this.originalRenderMethod;
},
remove: function () {
this.onLeave();
}
});
I have also created a glitch project which i am sharing here. Please feel free to join and collaborate in my project
Edit link: https://glitch.com/edit/#!/accessible-torpid-partridge
Site link:https://accessible-torpid-partridge.glitch.me
Thanks in advance

The code is correct, all you need is to tweak the exposed SSAOShader uniforms: SSAOPass.kernelRadius, SSAOPass.minDistance, SSAOPass.maxDistance - like in the Three.js example.
Keep in mind - the scale in the example is huge, so the values will need to be different in a default aframe scene.
It's a good idea to be able to dynamically update a component (via setAttribute() if you properly handle updates), so you can see what's going on in realtime. Something like I did here - SSAO in a-frame (also based on Don McCurdys gist.
I've used some basic HTML elements, most threejs examples use dat.GUI - it is made for demo / debug tweaks.

Related

Autodesk Forge AggregatedView viewing a "stitched" geometry rather than a smooth one

I have a simple forge app to view 3d models. At first, I initiated the forge viewer with GuiViewer3D class but then wanted to implement AggregatedView instead.
My problem is that AggregatedView shows the model correctly but it shows it as being "stitched" together. Whereas, if I use GuiViewer3D or Viewer3D, the model looks smooth and clean.
I have looked into the globalOffset but in any solution, the globalOffset is the same, and hence should not be the cause here.
This is how the model should look like (GuiViewer3D)
But this is how it looks like instea using AggregatedView
I am not quite sure what the issue here. I am using an .fbx file as the source of 3d model.
This the code of AggregatedView()
var view = new Autodesk.Viewing.AggregatedView();
function launchViewer(urn) {
var options = {
env: 'AutodeskProduction',
getAccessToken: getForgeToken
};
Autodesk.Viewing.Initializer(options, () => {
var htmlDiv = document.getElementById('forgeViewer');
view.init(htmlDiv, options);
var documentId = 'urn:' + urn;
view.unloadAll();
Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
}
function onDocumentLoadSuccess(doc) {
var nodes = doc.getRoot().search({ role:'3d', type: 'geometry' });
console.log(nodes);
view.setNodes(nodes[0]);
}
function onDocumentLoadFailure(viewErrorCode, viewErrorMsg) {
console.error('onDocumentLoadFailure() - errorCode:' + viewErrorCode + '\n- errorMessage:' + viewErrorMsg);
}
function getForgeToken(callback) {
fetch('/api/forge/oauth/token').then(res => {
res.json().then(data => {
callback(data.access_token, data.expires_in);
});
});
}
Many thanks in advance!
UPDATE:
After setting the global offset to (0,0,0), the geometry still looks "Stitched" together rather than smooth.
The pivot point is not the global offset. Please use viewer.getAllModels().map( model => model.getGlobalOffset() ) to check that instead. For AggreagateView, you can get the viewer instance via view.viewer;
In addition, AggreagateView loads models in the shared coordinate (applyRefPoint: true), so your model might be far away from the viewer's origin. Could you try this to see if it helps?
const options3d = {
getCustomLoadOptions: (bubble, data) => {
console.log(bubble, data);
return {
applyRefPoint: false //!<<< Disable Share Coordinate
// globalOffset: new THREE.Vector3( 543.0811920166016, 9.154923574611564, -1442.591747316564 ) //!<<< uncomment this to specify your globalOffset, but you need to include `applyRefPoint: false` above together.
// createWireframe: false
};
}
};
view.init(viewerDiv, options3d);

Autodesk Forge Viewer Api Cannot load markups inside screenshot

Good day,
I am using the latest Autodesk forge viewer and I am trying to take a screenshot that also renders my markups. Right now my code takes a screenshot without any markups. Below is my viewer code. I am loading markups Core and markups Gui extensions. Notice the "takeSnapshot(viewer)" function inside onDocumentLoadSuccess(viewerDocument). The function is defined right before the initializer function.
function takeSnapshot(target){
$('#snipViewer').click( () => {
target.getScreenShot(1600, 920, (blobURL) => {
let snip = blobURL;
$('#sniplink').attr("href", snip);
$('#sniplink').html('Not Empty');
$('#sniplink').css({"background-image": `url(${blobURL})`});
});
});
}
//Autodesk Viewer Code
instance.data.showViewer = function showViewer(viewerAccessToken, viewerUrn){
localStorage.setItem("viewerAccessTokentoken", viewerAccessToken);
localStorage.setItem("viewerUrn", viewerUrn);
var viewer;
var options = {
env: 'AutodeskProduction',
api: 'derivativeV2',
getAccessToken: function(onTokenReady) {
var token = viewerAccessToken;
var timeInSeconds = 3600;
onTokenReady(token, timeInSeconds);
}
};
Autodesk.Viewing.Initializer(options, function() {
let htmlDiv = document.getElementById('forgeViewer');
viewer = new Autodesk.Viewing.GuiViewer3D(htmlDiv);
let startedCode = viewer.start();
viewer.setTheme("light-theme");
viewer.loadExtension("Autodesk.CustomDocumentBrowser").then(() => {
viewer.loadExtension("Autodesk.Viewing.MarkupsCore");
viewer.loadExtension("Autodesk.Viewing.MarkupsGui");
});
if (startedCode > 0) {
console.error('Failed to create a Viewer: WebGL not supported.');
$("#loadingStatus").html("Failed to create a Viewer: WebGL not supported.");
return;
}
console.log('Initialization complete, loading a model next...');
});
var documentId = `urn:` + viewerUrn;
var derivativeId = `urn:` + instance.derivativeUrn;
Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
function onDocumentLoadSuccess(viewerDocument) {
var defaultModel = viewerDocument.getRoot().getDefaultGeometry();
viewer.loadDocumentNode(viewerDocument, defaultModel);
takeSnapshot(viewer);
}
function onDocumentLoadFailure() {
console.error('Failed fetching Forge manifest');
$("#loadingStatus").html("Failed fetching Forge manifest.");
}
}
I have already read this article: https://forge.autodesk.com/blog/screenshot-markups
I have tried doing this method but the instructions are very unclear for me. <div style="width:49vw; height:100vh;display:inline-block;"><canvas id="snapshot" style="position:absolute;"></canvas><button onclick="snaphot();" style="position:absolute;">Snapshot!</button></div>
What is the canvas element here for? Am I supposed to renderToCanvas() when I load the markups extension inside the initialize function or in my screenshot function? Is there some way I can implement the renderToCanvas() without changing too much of what I already am using here? I am not an expert with the viewer API so please if you could help me it would be very much appreciated, I am a beginner please don't skip many steps.
Thank you very much!
Here's a bit more simplified logic for generating screenshots with markups in Forge Viewer, with a bit more explanation on why it needs to be done this way below:
function getViewerScreenshot(viewer) {
return new Promise(function (resolve, reject) {
const screenshot = new Image();
screenshot.onload = () => resolve(screenshot);
screenshot.onerror = err => reject(err);
viewer.getScreenShot(viewer.container.clientWidth, viewer.container.clientHeight, function (blobURL) {
screenshot.src = blobURL;
});
});
}
function addMarkupsToScreenshot(viewer, screenshot) {
return new Promise(function (resolve, reject) {
const markupCoreExt = viewer.getExtension('Autodesk.Viewing.MarkupsCore');
const canvas = document.createElement('canvas');
canvas.width = viewer.container.clientWidth;
canvas.height = viewer.container.clientHeight;
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(screenshot, 0, 0, canvas.width, canvas.height);
markupCoreExt.renderToCanvas(context, function () {
resolve(canvas);
});
});
}
const screenshot = await getViewerScreenshot(viewer);
const canvas = await addMarkupsToScreenshot(viewer, screenshot);
const link = document.createElement('a');
link.href = canvas.toDataURL();
link.download = 'screenshot.png';
link.click();
Basically, the markups extension can only render its markups (and not the underlying 2D/3D scene) into an existing <canvas> element. That's why this is a multi-step process:
You render the underlying 2D/3D scene using viewer.getScreenShot, getting a blob URL that contains the screenshot image data
You create a new <canvas> element
You insert the screenshot into the canvas (in this case we create a new Image instance and render it into the canvas using context.drawImage)
You call the extension's renderToCanvas that will render the markups in the canvas on top of the screenshot image

Forge Viewer: Properties Window

The Properties window does not populate any properties even though the 2D view has properties info for the selected room
Here is the function that loads the model. what am I missing?
function loadModel() {
var initialViewable = viewables[indexViewable];
var svfUrl = lmvDoc.getViewablePath(initialViewable);
var modelOptions = {
sharedPropertyDbPath: lmvDoc.getFullPath(lmvDoc.getRoot().findPropertyDbPath())
};
viewer.loadModel(svfUrl, modelOptions, onLoadModelSuccess, onLoadModelError);
}
One line missing in your code, please try the following instead:
var sharedDbPath = initialViewable.findPropertyDbPath();
sharedDbPath = lmvDoc.getFullPath( sharedDbPath );
var modelOptions = {
sharedPropertyDbPath: sharedDbPath
};
However, you should not need to specify the sharedPropertyDbPath manually now. You can take advantage of the Viewer3D#loadDocumentNode to load the model directly. It will automatically determine the path for you. (started from v7 viewer)
const initialViewable = viewables[0];
viewer.loadDocumentNode( lmvDoc, initialViewable, loadOptions )
.then( onLoadModelSuccess )
.catch( onLoadModelError );

Why the web audio output from oscillator is not working as expected?

Here is the code:
I want to create an audio program that can play audio from very low frequency to high frequency.
However, this code results in different output (even with the same device):
The sound comes out suddenly - the expected result is it comes out gradually. I am sure my hearing is okay because I've asked my friends to hear;
The audio sounds different on the same frequency.
WARNING: Please adjust your volume to very low in case of any hurting before running this script.
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// create Oscillator node
var oscillator = audioCtx.createOscillator();
var osc_arr = [];
function purgeSound(){
osc_arr.forEach(function(v){
try {
v.stop();
v.disconnect(audioCtx.destination);
} catch (e) {}
})
}
function playSoundAtFreq(fq){
purgeSound();
var osc = audioCtx.createOscillator();
osc_arr.push(osc);
osc.type = 'square';
osc.frequency.setValueAtTime(fq, audioCtx.currentTime); // value in hertz
$('#fff').val(fq);
osc.connect(audioCtx.destination);
osc.start();
}
$('#stop').click(function(){
purgeSound();
_break = true;
})
var _break = false;
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
var pointer = 0;
var go = appendAttemptAsync(10000);
async function appendAttemptAsync(range) {
if(_break) return;
var target = pointer+range;
for (pointer; pointer<range; pointer++) {
playSoundAtFreq(pointer);
console.log(pointer)
//if(pointer % 1 == 0) {
await sleep(100)
//}
}
return 5221;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id='stop'>stop</button>
<input id="fff" type="text" />
WARNING: Please adjust your volume to very low in case of any hurting before running this script.
Thanks for any kind of suggestions to improve my code.
If you want an Oscillator to sweep like in the YouTube video that you mentioned, you can do something like:
let osc = new OscillatorNode(audioCtx);
osc.connect(audioCtx.destination);
osc.frequency.setValueAtTime(20, audioCtx.currentTime);
osc.frequency.linearRampToValueAtTime(audioCtx.sampleRate / 2, audioCtx.currentTime + 300);
osc.start();
Change the 300 to some appropriate time over which the tone sweeps. I arbitrarily chose 5 minutes.
I do not know why your example doesn't work, but this snippet is the typical way to sweep a tone using WebAudio.

Adding static image to Lightswitch HTML 2013 Browse Screen

In my case, I have color coded some tiles in the HTML client and I want to add a simple color code key. I have the PNG file I want to use.
I do not require the ability to upload or change the image.
This link seems to achieve what I am looking for, but I am not sure where to implement. Does all of this code go into the PostRender of the Image Control I created?
Add image to lightswitch using local property and file location
Here is what the PostRender of the simple Image data item I created as an Image Local Property, and then dragged into the Solution Designer. It was basically copied from the link above, but I did change the name of the image file to match mine, and I have already added the item to the Content\Images folder structure and it shows in the file view:
myapp.BrowseOrderLines.ColorKey_postRender = function (element, contentItem) {
// Write code here.
function GetImageProperty(operation) {
var image = new Image();
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
// XMLHttpRequest used to allow external cross-domain resources to be processed using the canvas.
// unlike crossOrigin = "Anonymous", XMLHttpRequest works in IE10 (important for LightSwitch)
// still requires the appropriate server-side ACAO header (see https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image)
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var url = URL.createObjectURL(this.response);
image.onload = function () {
URL.revokeObjectURL(url);
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
var dataURL = canvas.toDataURL("image/png");
operation.complete(dataURL.substring(dataURL.indexOf(",") + 1));
};
image.src = url;
};
xhr.open('GET', this.imageSource, true);
xhr.responseType = 'blob';
xhr.send();
};
myapp.BrowseOrderLines.ColorKey_postRender = function (element, contentItem) {
// Write code here.
msls.promiseOperation
(
$.proxy(
GetImageProperty,
{ imageSource: "content/images/Key.png" }
)
).then(
function (result) {
contentItem.screen.ImageProperty = result;
}
);
};
}
Currently, the Image control does show on the screen in the browser, and is the custom size I choose, but it is just a light blue area that does not display my image file.
I am not sure if I have embedded the image? I am not sure if that is a missing step?
Thank you!!
The easiest method of testing this approach would be to change your postRender to the following (which embeds the helper function within the postRender function):
myapp.BrowseOrderLines.ColorKey_postRender = function (element, contentItem) {
function GetImageProperty(imageSource) {
return msls.promiseOperation(
function (operation) {
var image = new Image();
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
// XMLHttpRequest used to allow external cross-domain resources to be processed using the canvas.
// unlike crossOrigin = "Anonymous", XMLHttpRequest works in IE10 (important for LightSwitch)
// still requires the appropriate server-side ACAO header (see https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image)
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var url = URL.createObjectURL(this.response);
image.onload = function () {
URL.revokeObjectURL(url);
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
var dataURL = canvas.toDataURL("image/png");
operation.complete(dataURL.substring(dataURL.indexOf(",") + 1));
};
image.onerror = function () {
operation.error("Image load error");
};
image.src = url;
};
xhr.open('GET', imageSource, true);
xhr.responseType = 'blob';
xhr.send();
}
);
};
GetImageProperty("content/images/Key.png").then(function onComplete(result) {
contentItem.screen.ImageProperty = result;
}, function onError(error) {
msls.showMessageBox(error);
});
};
This assumes that you named the local property ImageProperty as per my original post and the Image control on your screen is named ColorKey.
In the above example, I've also taken the opportunity to slightly simplify and improve the code from my original post. It also includes a simple error handler which may flag up if there is a problem with loading the image.
If this still doesn't work, you could test the process by changing the image source file-name to content/images/user-splash-screen.png (this png file should have been added as a matter of course when you created your LightSwitch HTML project).
As the GetImageProperty function is a helper routine, rather than embedding it within the postRender you'd normally place it within a JavaScript helper module. This will allow it to be easily reused without repeating the function's code. If you don't already have such a library and you're interested in implementing one, the following post covers some of details involved in doing this:
Lightswitch HTML global JS file to pass variable