I'm trying to build a free hand drawing application using the html canvas element , and so i'm trying to implement the 'Undo' feature which basically takes a snapshot of the current state of the canvas and then saves it on a list of some sort when the user draws something on the canvas, and when the user presses the undo button it pops the last state saved and draws in onto the canvas . I've tried calling context.drawImage() after image.onload as shown but still no use.
export class WhiteboardScreenComponent implements OnInit, AfterViewInit {
#ViewChild('myCanvas')
myCanvas: ElementRef<HTMLCanvasElement>;
painting: boolean = false;
strokeWidth: number = 2;
strokeColor: string = this.colors[this.selectedColor];
lineStyle: CanvasLineCap = 'round';
ctx: CanvasRenderingContext2D;
undoHistory: any[] = [];
constructor() { }
ngOnInit(): void { }
ngAfterViewInit(): void {
this.ctx = this.myCanvas.nativeElement.getContext('2d');
this.resizeCanvas();
this.myCanvas.nativeElement.addEventListener('mousedown', this.startPainting);
this.myCanvas.nativeElement.addEventListener('mouseup', this.finishPainting);
this.myCanvas.nativeElement.addEventListener('mousemove', this.draw);
}
startPainting = (e) => {
this.painting = true;
this.ctx.beginPath();
this.ctx.moveTo(e.clientX, e.clientY);
}
//saving the state of the canvas here
finishPainting = (e) => {
let src = this.myCanvas.nativeElement.toDataURL("image/png");
this.undoHistory.push(src);
this.ctx.closePath();
this.painting = false;
}
draw = (e) => {
if (!this.painting) return;
this.setProperties();
this.ctx.lineTo(e.clientX, e.clientY);
this.ctx.stroke();
}
setProperties(): void {
this.ctx.lineWidth = this.strokeWidth;
this.ctx.lineCap = this.lineStyle;
this.ctx.strokeStyle = this.strokeColor;
}
//fetching the last saved state and drawing it onto the canvas
undo(): void {
if (this.undoHistory.length > 0) {
let image = new Image();
image.onload = () => {
this.ctx.drawImage(image, 0, 0);
};
image.src = this.undoHistory.pop();
}
}
}
Before calling the drawImage() method, you're supposed to clear the screen , that can be accomplished by simply resizing the canvas to re-draw it, or calling clearRect().
Related
How can I make the 3d objects that I load into the map using The WebGL overlay API, to cast shadows on the map tiles and on other loaded objects?
It seems to me that this is not supported yet( or this feature is removed) so is there any workaround?
Preferred WebGL framework: ThreeJs
A workaround with threeJS would be to put a thin box(it will not work with planeGeometry) on the ground that is completely transparent but receives the shadow. this can be achieved by shadowMaterial
Note: this would only show the shadows from meshes added to the webgloverlay, not from the buildings on the map
We do not have this option yet on google map , because overlay of map we get from "webgloverlayview" do not have receive shadow option.
I tried to create my own plane over google map and achieve the shadow
my plane opacity is very light like 0.3. I also added image below in of colorful plane to explain.
++
Please see #yosan_melese answer it prefect to use
ShadowMaterial
here is example code
import { Loader } from '#googlemaps/js-api-loader';
import * as THREE from 'three';
import {FBXLoader} from 'three/examples/jsm/loaders/FBXLoader.js';
const apiOptions = {
apiKey: 'A***********8',
version: "beta",
map_ids: [""]
};
let directionalLight = '';
const mapOptions = {
"tilt": 0,
"heading": 0,
"zoom": 18,
"center": { lat: 33.57404, lng: 73.1637 },
"mapId": "" ,
"mapTypeId": 'roadmap'
}
/*
roadmap: the default map that you usually see.
satellite: satellite view of Google Maps and Google Earth, when available.
hybrid: a mixture of roadmap and satellite view.
terrain: a map based on terrain information, such as mountains and valleys.
*/
async function initMap() {
const mapDiv = document.getElementById("map");
const apiLoader = new Loader(apiOptions);
await apiLoader.load();
return new google.maps.Map(mapDiv, mapOptions);
}
let scene, renderer, camera, loader,loader1,controls;
function initWebglOverlayView(map) {
const webglOverlayView = new google.maps.WebglOverlayView();
webglOverlayView.onAdd = () => {
// set up the scene
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera();
const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 ); // soft white light
scene.add(ambientLight);
directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.x += (-90)
directionalLight.position.y += 0
directionalLight.position.z += 20
directionalLight.castShadow = true
const d = 100;
directionalLight.shadow.camera.left = - d;
directionalLight.shadow.camera.right = d;
directionalLight.shadow.camera.top = d;
directionalLight.shadow.camera.bottom = - d;
scene.add(directionalLight);
scene.add( new THREE.CameraHelper( directionalLight.shadow.camera ) );
// FLOOR
/*
const plane = new THREE.Mesh(new THREE.PlaneGeometry(2050, 2200, 300),
new THREE.MeshPhongMaterial({ color: 0xF3F4F5, opacity: 0.3, transparent: true}));
plane.rotation.x = 0;
plane.rotation.y = 0;
plane.rotation.z = 0;
plane.castShadow = true
plane.receiveShadow = true
scene.add(plane);
*/
//after yosan_melese answer i am using ShadowMaterial
const geometry = new THREE.PlaneGeometry( 2000, 2000 );
geometry.rotateX( - Math.PI / 2 );
const material = new THREE.ShadowMaterial();
material.opacity = 0.5;
const plane = new THREE.Mesh( geometry, material );
plane.rotation.x = 2;
plane.position.x = 0;
plane.position.y = 0;
plane.position.z = 0;
plane.receiveShadow = true;
scene.add( plane );
loader = new FBXLoader();
loader.load( 'model/name_model.fbx', function ( object ) {
object.scale.set( 1, 1, 1 );
object.rotation.x = 1.480;
object.rotation.y = 0.950;
object.rotation.z = 0.070;
object.castShadow = true;
object.receiveShadow = true;
object.name = "name_model";
object.traverse( function ( child ) {
if(child.isMesh ) {
child.castShadow = true;
child.receiveShadow = true;
}
});
scene.add( object );
});
}
webglOverlayView.onContextRestored = (gl) => {
// create the three.js renderer, using the
// maps's WebGL rendering context.
renderer = new THREE.WebGLRenderer({
canvas: gl.canvas,
context: gl,
...gl.getContextAttributes(),
});
renderer.autoClear = false;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.receiveShadow = true;
renderer.castShadow = true;
}
webglOverlayView.onDraw = (gl, coordinateTransformer) => {
// update camera matrix to ensure the model is georeferenced correctly on the map
const matrix = coordinateTransformer.fromLatLngAltitude(mapOptions.center, 10);
camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
webglOverlayView.requestRedraw();
renderer.render(scene, camera);
// always reset the GL state
renderer.resetState();
}
webglOverlayView.setMap(map);
}
(async () => {
const map = await initMap();
initWebglOverlayView(map);
})();
after #yosan_melese code
When you export from Blender to JSON the model is displayed in gray in all browsers. Using (github.com/mrdoob/three.js/tree/master/utils/exporters/blender)
I'm attaching here the export settings, screenshot from the Chrome and JS.
How to solve this problem?
Thank you for your tips!
Screenshot from Chrome
Export settings:
Settings page
JS:
var scene, camera, renderer;
var WIDTH = 300;
var HEIGHT = 300;
var SPEED = 0.03;
function init() {
scene = new THREE.Scene();
initMesh();
initCamera();
initLights();
initRenderer();
document.body.appendChild(renderer.domElement);
}
function initCamera() {
camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, 1, 10);
camera.position.set(0, 3.5, 5);
camera.lookAt(scene.position);
}
function initRenderer() {
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(WIDTH, HEIGHT);
}
function initLights() {
var light = new THREE.AmbientLight(0xffffff);
scene.add(light);
}
var mesh = null;
function initMesh() {
var loader = new THREE.JSONLoader();
loader.load('./sp.json', function(geometry, materials) {
mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
mesh.scale.x = mesh.scale.y = mesh.scale.z = 0.75;
mesh.translation = THREE.GeometryUtils.center(geometry);
scene.add(mesh);
});
}
function rotateMesh() {
if (!mesh) {
return;
}
mesh.rotation.x -= 0;
mesh.rotation.y -= SPEED;
mesh.rotation.z -= 0;
}
function render() {
requestAnimationFrame(render);
rotateMesh();
renderer.render(scene, camera);
}
init();
render();
If you created your model textures with node editor then is possible you don't see textures, how about UV unwrap ?
I'm new to Flash, Starling, and Feathers and am giving myself a kind of crash course, but am getting confused. I simply want my root starling class to initiate my game screen. I apologize if this is noobish. I really do want to understand.
I am not sure what dispatches FeathersEventType.INITIALIZE and I'm having trouble dispatching it manually. Any pointers in the right direction would be greatly appreciated.
In my Main.as (my Document Class), I instantiate starling, wait for the Root to be created, and then call its Start function (saw this in an example of how to show an initial background, not sure if it's best practice).
this.mStarling = new Starling(Root, this.stage, viewPort);
... //set background from embed
mStarling.addEventListener(starling.events.Event.ROOT_CREATED,
function(event:Object, app:Root):void
{
mStarling.removeEventListener(starling.events.Event.ROOT_CREATED, arguments.callee);
removeChild(background);
background = null;
var bgTexture:Texture = Texture.fromEmbeddedAsset(
backgroundClass, false, false);
//app.start(bgTexture, assets);
app.start(bgTexture, assets) // call the START on my root class
mStarling.start();
});
Here's my Root class (which is the root class passed to Starling)
public function Root()
{
if (verbose) trace(this + "Root(" + arguments);
super();
this.addEventListener(FeathersEventType.INITIALIZE, initializeHandler);
}
// this is not being called
private function initializeHandler(e:Event):void
{
this._navigator.addScreen(GAME_SCREEN, new ScreenNavigatorItem(GameScreen,
{
complete: MAIN_MENU
}));
}
public function start(background:Texture, assets:AssetManager):void
{
sAssets = assets; // assets loaded on document class
addChild(new Image(background)); // passed from doc class
this._navigator = new ScreenNavigator();
this.addChild(this._navigator);
var progressBar:ProgressBar = new ProgressBar(300, 20);
progressBar.x = (background.width - progressBar.width) / 2;
progressBar.y = (background.height - progressBar.height) / 2;
progressBar.y = background.height * 0.85;
addChild(progressBar);
// Progress bar while assets load
assets.loadQueue(function onProgress(ratio:Number):void
{
progressBar.ratio = ratio;
if (ratio == 1)
Starling.juggler.delayCall(function():void
{
gameScreen = new GameScreen();
trace("got this far" + gameScreen);
gameScreen.GAME_OVER.add(gotoGameOverScreen);
gameOverScreen = new GameOverScreen();
gameOverScreen.PLAY_AGAIN.add(gotoGameScreen);
progressBar.removeFromParent(true);
// This is where I'd like the GAME_SCREEN to show.
// now would be a good time for a clean-up
System.pauseForGCIfCollectionImminent(0);
System.gc();
}, 0.15);
});
This is part of my Root Class
public function start(background:Texture, assets:AssetManager):void
{
stageW= int(stage.stageWidth);
stageH = int(stage.stageHeight);
sAssets = assets;
bgIma=new Image(background);
bgIma.height=g.stageH * 0.4;
bgIma.scaleX=bgIma.scaleY;
bgIma.x=(g.stageW-bgIma.width)>>1;
bgIma.y=(g.stageH-bgIma.height)>>1;
addChild(bgIma);
var progressBar:ProgressBar = new ProgressBar(175, 20);
progressBar.x = (g.stageW - progressBar.width) >> 1;
progressBar.y = (g.stageH - progressBar.height) >> 1;
progressBar.y = g.stageH * 0.8;
addChild(progressBar);
assets.loadQueue(function onProgress(ratio:Number):void
{
progressBar.ratio = ratio;
if (ratio == 1)
Starling.juggler.delayCall(function():void
{
progressBar.removeFromParent(true);
removeChild(bgIma);
bgIma=null;
addedToStageHandler();
addSounds();
System.pauseForGCIfCollectionImminent(0);
System.gc();
}, 0.15);
});
}
protected function addedToStageHandler(event:starling.events.Event=null):void
{
this._navigator = new ScreenNavigator();
_navigator.autoSizeMode = ScreenNavigator.AUTO_SIZE_MODE_CONTENT;
this.addChild( this._navigator );
this._transitionManager = new ScreenFadeTransitionManager(_navigator);
this._navigator.addScreen( "Fluocode", new ScreenNavigatorItem(Fluocode));
this._navigator.addScreen( "Main", new ScreenNavigatorItem(AppMain));
this._navigator.showScreen("Fluocode");
this._transitionManager.duration = 0.5;
this._transitionManager.ease = Transitions.EASE_IN;
}
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();
}
<script type="text/javascript">
var canvas, context, tool, e;
var varblurup=0;
var varsizeup=1;
var swtchclr;
// Keep everything in anonymous function, called on window load.
if(window.addEventListener) {
window.addEventListener('load', function () {
//check tool is pen or line or shape
var chktool="pen";
function init () {
alert("Line3");
// Find the canvas element.
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
//varblurup=10;
context.shadowColor = 'colour';
context.shadowBlur = 0;
context.lineWidth=1;
context.lineJoin = 'miter';
context.miterLimit = 4;
this.context.save();
// Pencil tool instance.
//tool = new tool_pencil();
//alert("Pen");
if(chktool=="pen")
{ tool = new tool_pencil();
alert("Pen");
}else if (chktool=="line")
{
tool2 = new tool_line();
alert("Line");
}
// Attach the mousedown, mousemove and mouseup event listeners.
canvas.addEventListener('mousedown', ev_canvas, false);
canvas.addEventListener('mousemove', ev_canvas, false);
canvas.addEventListener('mouseup', ev_canvas, false);
}
// This painting tool works like a drawing pencil which tracks the mouse
// movements.
function tool_pencil () {
var tool = this;
this.started = false;
// This is called when you start holding down the mouse button.
// This starts the pencil drawing.
this.mousedown = function (ev) {
context.beginPath();
context.moveTo(ev._x, ev._y);
tool.started = true;
};
// This function is called every time you move the mouse. Obviously, it only
// draws if the tool.started state is set to true (when you are holding down
// the mouse button).
this.mousemove = function (ev) {
if (tool.started) {
context.lineTo(ev._x, ev._y);
//this.style('stroke-opacity').value
context.stroke();
}
};
// This is called when you release the mouse button.
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
}
};
}
// The general-purpose event handler. This function just determines the mouse
// position relative to the canvas element.
function ev_canvas (ev) {
if (ev.layerX || ev.layerX == 0) { // Firefox
ev._x = ev.layerX;
ev._y = ev.layerY;
} else if (ev.offsetX || ev.offsetX == 0) { // Opera
ev._x = ev.offsetX;
ev._y = ev.offsetY;
}
// Call the event handler of the tool.
var func = tool[ev.type];
if (func) {
func(ev);
}
}
init();
}, false); }
I found the Mozilla canvas tutorial to be a helpful guide. It covers most areas including shape drawing:
https://developer.mozilla.org/en/Canvas_tutorial
https://developer.mozilla.org/en/Canvas_tutorial/Drawing_shapes
It is quite an extended question so here are a couple of links about it..
HTML5 Canvas: Shape Drawing
A Quick Introduction to the HTML 5 Canvas
A Quick Introduction to the HTML 5 Canvas – Part Two