html5 canvas in durandal - html

i am trying to use the html5 canvas element to draw in durandal without success
the markup
<body>
<canvas id="canvas"></canvas>
</body>
the js
define(function() {
function viewAttached() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var buffer = document.createElement('canvas');
buffer.width = buffer.height = 60;
var bctx = buffer.getContext('2d');
bctx.translate(30, 30);
bctx.rotate(0.5);
bctx.fillStyle = 'rgb(255, 0, 0)';
bctx.fillRect(-15, -15, 30, 30);
ctx.fillStyle = 'rgb(0, 255, 0)';
ctx.fillRect(30, 30, 30, 30);
ctx.drawImage(buffer, 50, 50);
}
var ctor = function () {
this.viewAttached = viewAttached();
return ctor;
};
});
the problem is that canvas doesn't load so all what i can see is an empty canvas. the same code is working well in jsfiddle i mean only jquery and canvas but when i transfer it to durandal it doesnt work anymore can anybody help?

In the comments you mentioned it is working with viewAttached(), but in fact in your question you mention that it is indeed not working. My best bet is that A: nemesv is right and you may be firing that function, but not in the way you intend and B: Your view isn't actually attached when you call viewAttached.
This works in Durandal 1.2 -
define(function() {
function viewAttached() {
// Do stuff
}
var viewModel = {
viewAttached: viewAttached;
};
return viewModel;
});
If you are using Durandal 2.0, do it like this -
define(function() {
function attached() {
// Do stuff
}
var viewModel = {
attached: attached;
};
return viewModel;
});
That assumes you are using the router. You don't need to fire the constructor, Durandal handles that for you. You also don't need to call viewAttached, because Durandal handles that at the proper point in the loading cycle (after the DOM is ready) http://durandaljs.com/documentation/Using-The-Router/

Related

Forge MarkupUtils renderToCanvas with multiple layers?

We currently have a PDF loaded into Forge viewer with multiple markup layers (created using a custom DrawMode), each layers visibility toggleable.
We want to give the user the ability of printing what they currently see (the PDF with the layered markup). I was able to find posts offering potential solutions for printing (using canvas, getScreenshot and MarkupUtils renderToCanvas).
Example post: Autodesk Forge get screenshot with markups
The solution at first looked to be working great but I've noticed only one of our markup layers is ever rendered to the canvas (seemingly the last one added), the other layers are ignored.
All markups are loaded and are visible on screen. Additionally if I hide that layer, it is still printed.
Is there any way to add the markup from all loaded markup layers using renderToCanvas?
Or any potential known workaround?
Any help appreciated. Thanks in advance.
Code snippet of the function I've wrote which works (but loading the most recently added layer only).
export const printViewerToPDF = (markupsCore: MarkupsCore, jsPDF: any) => {
// Create new image
var screenshot = new Image();
// Get the canvas element
var canvas = document.getElementById('snapshot') as HTMLCanvasElement;
// Fit canvas to match viewer
if (canvas) {
canvas.width = markupsCore.bounds.width;
canvas.height = markupsCore.bounds.height;
// Create a context
var ctx = canvas.getContext('2d');
// Clear
if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(screenshot, 0, 0, canvas.width, canvas.height);
}
// Screenshot viewer and render to canvas
markupsCore.viewer.getScreenShot(canvas.width, canvas.height, function(
blobUrl: any,
) {
screenshot.onload = function() {
if (ctx) {
ctx.drawImage(screenshot, 0, 0);
}
};
screenshot.src = blobUrl;
});
// Render markup to canvas
setTimeout(function() {
markupsCore.renderToCanvas(ctx, function() {
var pdf = new jsPDF('l', 'px', [canvas.height, canvas.width]);
pdf.addImage(ctx!.canvas, 0, 0, canvas.width, canvas.height);
pdf.save(Date.now().toString() + '.pdf');
});
}, 300);
}
};
Here's what the renderToCanvas method looks like (you can find it in https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/extensions/Markup/Markup.js):
MarkupsCore.prototype.renderToCanvas = function(context, callback, renderAllMarkups) {
var width = this.bounds.width;
var height = this.bounds.height;
var viewBox = this.getSvgViewBox(width, height);
var numberOfScreenshotsTaken = 0;
var markups = [];
var layer;
var onMarkupScreenshotTaken = function () {
if (callback && (++numberOfScreenshotsTaken === markups.length)) {
callback();
}
}.bind(this);
if (renderAllMarkups) {
var svgKeys = Object.keys(this.svg.childNodes);
var layersKeys = Object.keys(this.svgLayersMap);
// Append only markups that their parent layer is contained inside the svg main container.
for (var i = 0; i < svgKeys.length; i++) {
for (var j = 0; j < layersKeys.length; j++) {
layer = this.svgLayersMap[layersKeys[j]];
if (this.svg.childNodes[svgKeys[i]] === layer.svg) {
markups = markups.concat(layer.markups);
}
}
}
} else {
layer = this.svgLayersMap[this.activeLayer] || this.editModeSvgLayerNode;
markups = layer.markups;
}
if (markups.length === 0) {
callback();
} else {
markups.forEach(function(markup) {
markup.renderToCanvas(context, viewBox, width, height, onMarkupScreenshotTaken);
});
}
};
As you can see, if you leave the 3rd parameter undefined or set to false, only markups from the active layer will be rendered. If you set the 3rd parameter to true, markups from all layers should be rendered.
Try stepping into the method yourself and double-check the list of markups towards the end, before markup.renderToCanvas is called for every item in the list.

THEE.JSONLoader Object as public variable

I was wondering if its possible to add an object that is loaded via THREE.JSONLoader as public variable? Ideally, I'd like all my loaded textures, material creation, and geometry to be public variables at the top of my script for easy manipulation (I have strong 3D background but new to js and webGL). I'm finding that my public vars that are declared something - are no longer public once they're added as a parameter to a function - in this case the JSONLoader. However, just naming a var, without declaring its value "runs" but I get weird THREE.min.js error I can't comprehend. I've included my code below - know it has other issues - please feel free to let me know how bad it is - it helps me learn :)
//webGL
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
camera.position.set(0, 16, 25);
camera.rotation.x += -0.32;
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
cubeCamera = new THREE.CubeCamera(1, 1000, 256); // parameters: near, far, resolution
cubeCamera.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter; // mipmap filter
scene.add(cubeCamera);
///LOADERS
var loadTexture = new THREE.TextureLoader();
var loaderJs = new THREE.JSONLoader();
///TEXTURES
var skyTexture = loadTexture.load("textures/background.jpg");
var seatTexture = loadTexture.load("textures/abc_Diffuse.jpg");
///MATERIALS
var skyMaterial = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
map: skyTexture
});
var frameMaterial = new THREE.MeshLambertMaterial({
//envMap: cubeCamera.renderTarget,
color: 0xffffff
});
var seatMaterial = new THREE.MeshLambertMaterial({
map: seatTexture
});
///GEOMETRY and MESHES
var frameGeo;
var skyGeo = new THREE.SphereGeometry(30, 30, 30);
var skySphere = new THREE.Mesh(skyGeo, skyMaterial);
scene.add(skySphere);
loaderJs.load("models/stoolFrame.js", function (){
frameGeo = new THREE.Mesh(frameGeo, frameMaterial);
frameGeo.scale.set(.5, .5, .5);
barStool.add(frameGeo);
});
loaderJs.load("models/stoolSeat.js", function (seatGeo){
seatGeo = new THREE.Mesh(seatGeo, seatMaterial);
seatGeo.scale.set(.5, .5, .5);
barStool.add(seatGeo);
});
var barStool = new THREE.Object3D();
scene.add(barStool);
var render = function () {
requestAnimationFrame(render);
barStool.rotation.y += 0.01;
frameGeo.visible = false;
cubeCamera.position.copy(frameGeo.position);
cubeCamera.updateCubeMap(renderer, scene);
frameGeo.visible = true;
renderer.render(scene, camera);
};
render();
When loading your stoolFrame, you forgot to add the parameters for the callback function, check the documentation example. The callback function takes a geometry and a material. When loading the stoolSeat you forgot the material aswell (provided you have a material for the stool)
var frameGeometry;
var frameMaterial;
var frameMesh;
loaderJs.load("models/stoolFrame.js", function (geometry, material){
frameGeometry = geometry;
frameMaterial = material
frameMesh = new THREE.Mesh(frameGeometry, frameMaterial);
frameMesh.scale.set(.5, .5, .5);
barStool.add(frameMesh);
});
Above is an example on how to make the geometry, material and mesh into global variables. However you probably dont need to save the geometry and material as global vars (you can access them from the mesh anyway: frameMesh.material; frameMesh.geometry).
If the parameter names are the same as a global variable name, javascript will use the parameter variable instead of the global one when trying to access it.

Displaying a loading spinner when drawing HTML5 canvas. Event?

I have spent many hours searching for this, but either it's not a very common problem or my Google-fu is lacking.
The short version is I have site with an HTML5 canvas element that is displaying large complex drawing, which means it usually takes up to 10 seconds to complete.
How is it possible to hook into the drawing event? I can't find anything of the sorts, but it would really help user experience if a loading spinner was displayed as the canvas was being drawed. I have seen it done before, but not seen the "how".
Details for the experienced:
I am displaying a PDF with PDFJS, and have my own viewer instead of the supplied one. It works, but sometimes it draws the PDF very slow, hence the need of a spinner.
Here is a really simple and nasty example this now uses an event which you can fire off :).
jsFiddle : https://jsfiddle.net/CanvasCode/jwzLr4wh/1/
javascript
image = new Image();
image.src = "https://upload.wikimedia.org/wikipedia/commons/1/1e/Large_Siamese_cat_tosses_a_mouse.jpg";
var canvas = document.getElementById('myCanvas-1');
var ctx = document.getElementById('myCanvas-1').getContext('2d');
var loading = true;
var loadingText = "Loading";
var event = new Event('loading');
// Listen for the event.
canvas.addEventListener('loading', function (e) {
// Quick and nasty
setInterval(function () {
if (loading) {
console.log("update");
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, 400, 400);
ctx.font = '15pt Arial ';
ctx.fillStyle = "#000";
ctx.fillText(loadingText, 200, 200);
if (loadingText == "Loading...") {
loadingText = "Loading";
}
loadingText = loadingText + "."
}
}, 1);
}, false);
canvas.dispatchEvent(event);
setTimeout(function () {
ctx.drawImage(image, 0, 0);
ctx.font = '15pt Arial ';
ctx.fillStyle = "#000";
loading = false;
}, 5000);
This now uses an event which you can fire off manually which will set the canvas to loading.

ThreeJS load time and texture rendering problems

I have a 3D object of a chair made in Blender and exported as a .obj and .mtl. First of all, the load time is a horrendous 40+ seconds; I have no idea why (Visual Studio possibly?) Second, the images textures are not loading properly. With no ambient or directional lighting I get a silhouette. With the lighting I get a slight hint of gray to give a little depth but nothing close to the many colors in the original object. I have only been working with ThreeJS for a few days now so I'm quite new to it. Hence, I am at a loss. I have read several artciles related to my issue but none seem to solve the problem. I even went so far as to add an addon to Blender to export to .js. I could not get it to even load properly (I assume it had to do with the fact I kept the same loader info from the .OBJMTLLoader and the .JSONLoader doesn't support or needs more than what I gave; I just am not familiar enough with it know). Any ideas/suggestions? Here is my script:
`
<script src="js/three.min.js"></script>
<script src="js/controls/TrackballControls.js"></script>
<script src="js/loaders/MTLLoader.js"></script>
<script src="js/loaders/OBJMTLLoader.js"></script>
<script src="js/Detector.js"></script>
<script>
if (!Detector.webgl) Detector.addGetWebGLMessage();
var container;
var camera, controls, scene, renderer;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.01, 1e10);
camera.position.z = 2;
//controls
controls = new THREE.TrackballControls(camera);
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5;
controls.panSpeed = 2;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
scene = new THREE.Scene();
scene.add(camera);
//lights
var ambient = new THREE.AmbientLight(0xCCCCCC);
scene.add(ambient);
var directionalLight = new THREE.DirectionalLight(0xCCCCCC);
directionalLight.position.set(0, 0, 2).normalize();
scene.add(directionalLight);
//main img
var material = new THREE.MeshBasicMaterial({ color: '0xCCCCCC' });
var loader = new THREE.OBJMTLLoader();
loader.addEventListener('load', function (event) {
var geometry = event.content;
//var mesh = new THREE.Mesh(geometry);
scene.add(geometry, material);
});
loader.load('chair.obj', 'chair.mtl');
// renderer
renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
//
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
controls.handleResize();
}
function animate() {
requestAnimationFrame(animate);
render();
controls.update();
}
function render() {
var timer = Date.now() * 0.0005;
renderer.render(scene, camera);
}
</script>
`
Well, you are defining a gray MeshBasicMaterial to use with your obj instead of using the material(s) that the loader will create for you, i guess.
Just use this. The "object" should have the material defined in your mtl-file.
loader.addEventListener( 'load', function ( event ) {
var object = event.content;
scene.add( object );
});
loader.load( 'chair.obj', 'chair.mtl' );
Concerning your load time: Could be your local server. Also, be aware that the obj file format ist quite big in raw ascii format. Also, if the mtl-file defines textures, they are maybe quite big in file size, too.
#GuyGood was correct in my case for the long load time. I was using the python simplehttpserver and switch to a node server and no more slow texture loading problems. Check out the threejs wiki for other local server options.

Swapping between layers in kineticJS

I'm trying to treat layers as pages -- i.e. I draw on one page, then turn the page and draw on another, each time storing the previous page in case the user goes back to it.
In my mind this translates as:
Create current_layer global pointer.
Each time newPage() is called, store the old layer in an array, and overwrite the pointer
layer_array.push(current_layer); //store old layer
current_layer = new Kinetic.Layer(); //overwrite with a new
New objects are then added to the current_layer which binds them to the layer, whether they are drawn or not. (e.g. current_layer.add(myCircle) )
Retrieving a page is simply updating the pointer to the requesting layer in the array, and redrawing the page. All the child nodes attached to the layer will also be drawn too
current_layer = layer_array[num-1]; //num is Page 2 e.g
current_layer.draw()
However nothing is happening! I can create new pages, and store them appropriately - but I cannot retrieve them again...
Here's my full code (my browser is having problems using jsfiddle):
<html>
<head>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.3.0.min.js"></script>
<script>
//Global
var stage; //canvas
var layer_array = [];
var current_page; //pointer to current layer
window.onload = function() {
stage = new Kinetic.Stage({
container: 'container',
width: 400,
height: 400
});
//Add initial page to stage to draw on
newPage()
};
//--- Functions ----//
function newPage(){
if(!current_page){
console.log("current page undefined");
} else {
layer_array.push(current_page);
// stage.remove(current_page);
//Nope, not working.
stage.removeChildren();
//Works, but I think it unbinds all objects
// from their specific layers...
// stage.draw()
console.log("Stored layer and removed it from stage");
}
current_page = new Kinetic.Layer();
console.log("Currently on page:"+(layer_array.length+1));
stage.add(current_page);
stage.draw();
}
function gotoPage(num){
stage.removeChildren()
stage.draw()
num = num-1;
if(num >= 0) {
current_page = layer_array[num];
console.log("Now on page"+(num+1));
stage.add(current_page);
stage.draw();
}
}
function addCircletoCurrentPage()
{
var rand = Math.floor(3+(Math.random()*10));
var obj = new Kinetic.Circle({
x: rand*16, y: rand*16,
radius: rand,
fill: 'red'
})
var imagelayer = current_page;
imagelayer.add(obj);
imagelayer.draw();
}
</script>
</head>
<body>
<div id="container"></div>
<button onclick="addCircletoCurrentPage()" >click</button>
<button onclick="newPage()" >new</button>
<button onclick="gotoPage(1)" >page1</button>
<button onclick="gotoPage(2)" >page2</button>
<button onclick="gotoPage(3)" >page3</button>
</body>
</html>
This was a fun problem. I think this fixes your troubles: http://jsfiddle.net/LRNHk/3/
Basically, you shouldn't remove() or removeChildren() as you risk de-referencing them.
Instead you should use:
layer.hide(); and layer.show();
this way, you keep all things equal and you get speedy draw performance.
So your go to page function should be like this:
function gotoPage(num){
for(var i=0; i<layer_array.length; i++) {
layer_array[i].hide();
}
layer_array[num].show();
console.log("Currently on page:"+(num));
console.log("Current layer: " + layer_array[num].getName());
stage.draw();
}
I also modified your other functions, which you can see in the jsfiddle.
Okay I changed my approach and instead of swapping layers (100x easier and makes more sense), I instead opted for serializing the entire stage and loading it back.
It works, but it really shouldn't have to be like this dammit
//Global
var stage; //canvas
var layer_array = [];
var current_page; //pointer to current layer
var page_num = 0;
window.onload = function() {
stage = new Kinetic.Stage({
container: 'container',
width: 400,
height: 400
});
//Add initial page to stage to draw on
newPage()
};
//--- Functions ----//
function newPage(){
if(!current_page){
console.log("current page undefined");
} else {
savePage(page_num)
stage.removeChildren()
console.log("Stored layer and removed it from stage");
}
current_page = new Kinetic.Layer();
console.log("Currently on page:"+(layer_array.length+1));
stage.add(current_page);
stage.draw();
page_num ++;
}
function savePage(num){
if( (num-1) >=0){
var store = stage.toJSON();
layer_array[num-1] = store;
console.log("Stored page:"+num)
}
}
function gotoPage(num){
savePage(page_num);
stage.removeChildren()
if(num-1 >= 0) {
var load = layer_array[num-1];
document.getElementById('container').innerHTML = ""; //blank
stage = Kinetic.Node.create(load, 'container');
var images = stage.get(".image");
for(i=0;i<images.length;i++)
{
//function to induce scope
(function() {
var image = images[i];
var imageObj = new Image();
imageObj.onload = function() {
image.setImage(imageObj);
current_page.draw();
};
imageObj.src = image.attrs.src;
})();
}
stage.draw();
page_num =num //update page
}
}
function addCircletoCurrentPage()
{
var rand = Math.floor(3+(Math.random()*10));
var obj = new Kinetic.Circle({
x: rand*16, y: rand*16, name: "image",
radius: rand,
fill: 'red'
})
var imagelayer = current_page;
imagelayer.add(obj);
imagelayer.draw();
}