Can't rotate loaded SVG in p5js - svg

I'm trying to load an external SVG with
I'm using p5js-svg with p5js and for the most part I don't have any issues, but I'm stuck trying to rotate a loaded SVG. I'm working with both the last versions of pj5s and the library (p5js v1.4.1 and p5js-svg v1.3.3).
Here is the code to reproduce:
function preload() {
svgs = [
loadSVG('/img/plantilla_01.svg'),
loadSVG('/img/plantilla_02.svg'),
loadSVG('/img/plantilla_03.svg')
]}
function setup() {
createCanvas(windowWidth, windowHeight, SVG)
noLoop()
angleMode(DEGREES)
imageMode(CENTER)
}
function draw() {
let posX = 0
let posY = 0
let imagen
for (let index = 0; index < 10; index++) {
let giro = random(-90, 90)
push()
translate(width/2, height/2)
rotate(giro)
imagen = svgs[floor(random(0,3))]
image(imagen, posX, posY, width/10, height/10)
posX += width/10
posY += height/10
pop()
}
}
The loaded SVGs are Adobe Illustrator-produced, and there's no issue with loading and displaying them. Which can be the issue with the rotation?

I think there's a bug with transforms not being applied to image() with the SVG renderer and loadSVG() (and I see you've already raised an issue on github).
One workaround could be using loadImage() instead of loadSVG():
function preload() {
svgs = [
loadImage('/img/plantilla_01.svg'),
loadImage('/img/plantilla_02.svg'),
loadImage('/img/plantilla_03.svg')
]}
function setup() {
createCanvas(windowWidth, windowHeight, SVG)
noLoop()
angleMode(DEGREES)
imageMode(CENTER)
}
function draw() {
let posX = 0
let posY = 0
let imagen
for (let index = 0; index < 10; index++) {
let giro = random(-90, 90)
push()
translate(width/2, height/2)
rotate(giro)
imagen = svgs[floor(random(0,3))]
image(imagen, posX, posY, width/10, height/10)
posX += width/10
posY += height/10
pop()
}
}

I ran into the same issue and used Javascript to fix it. Note that inline CSS only worked in the browser.
function addRotationHack(ele = "rect, circle, ellipse, line, polyline, polygon, path") {
// select shapes in the SVG
var elements = document.querySelectorAll(ele);
// loop through them
for (let i = 0; i < elements.length; i++) {
let deg = Math.random() * 360;
// ❌ Works in the browser, but not Illustrator (doesn't support inline CSS(?))
// elements[i].style.transform = `rotate(${deg}deg)`;
// ✅ instead set the property directly
elements[i].setAttribute("transform", `rotate(${deg})`);
}
}
// ... then call in draw() after your image
image(imagen, posX, posY, width/10, height/10)
addRotationHack();

Related

How are trigonometric functions used in this sketch to make one object move towards another?

So I asked a someone to help me with a small project ( ball chases ball you control one)
and he gave me a trigonometric thing, it works very well can someone explain trigonometry to me?
Heres the code he gave me:
var aiX = 0;
var aiY = 0;
var playerX = 200;
var playerY = 200;
draw = function () {
background(255);
playerX = mouseX;
playerY = mouseY;
fill(0, 255, 0);
ellipse(playerX, playerY, 50, 50);
fill(255, 0, 0);
ellipse(aiX, aiY, 50, 50);
var angle = atan2(playerY - aiY, playerX - aiX);
aiX += cos(angle);
aiY += sin(angle);
if (dist(playerX, playerY, aiX, aiY) < 50) {
text("ouch!", 100, 100);
}
};
How i changed it:
var aiX = 200;
var aiY = 0;
function setup() {
playerX = 200;
playerY = 200;
}
draw = function () {
createCanvas(400, 400);
background(255);
let h = hour();
//scare anyone
if (h === 3) {
console.log("I SEE YOU");
let img = createImg(imageData, "YOU CANT HIDE EVEN IF I WONT LOAD");
}
if (isKeyPressed && keyCode === UP_ARROW) {
playerY = playerY - 5;
}
if (isKeyPressed && keyCode === DOWN_ARROW) {
playerY = playerY + 5;
}
if (isKeyPressed && keyCode === RIGHT_ARROW) {
playerX = playerX + 5;
}
if (isKeyPressed && keyCode === LEFT_ARROW) {
playerX = playerX - 5;
}
fill(0, 255, 0);
ellipse(playerX, playerY, 50, 50);
fill(255, 0, 0);
ellipse(aiX, aiY, 50, 50);
function reset() {
playerX = 200;
playerY = 200;
aiX = 200;
aiY = 15;
}
var angle = atan2(playerY - aiY, playerX - aiX);
aiX += cos(angle);
aiY += sin(angle);
if (dist(playerX, playerY, aiX, aiY) < 50) {
console.log("GAMEOVER");
reset();
}
};
const imageData =
"data:image/jpeg;base64,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";
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
I just started learning p5js about a week ago, so sorry if my code is bad or sloppy...
Editor:https://editor.p5js.org/
Ironically the use of trigonometry in this example is actually unnecessary. The objective is to move a circle positioned at aiX, aiY closer to a second circle positioned at payerX, playerY. It is trivial to compute a vector from the ai position to the player position by taking the difference between the two components in each direction: playerX - aiX is the offset from ai to player in the horizontal direction, and playerY - aiY is the offset from ai to player in the vertical direction. Obviously you won't want to change the position of the ai circle by the entire offset, since that would be instant game over. If you want to fix the speed of movement at a particular level, you simply need to divide each component by the length of the vector, and then if you want a speed other than 1, multiply by the desired speed. Here is the code that does this without any trigonometry:
let aiX = 200;
let aiY = 0;
let playerX = 200;
let playerY = 200;
function setup() {
createCanvas(400, 400);
}
function draw() {
background(255);
if (isKeyPressed && keyCode === UP_ARROW) {
playerY = playerY - 5;
}
if (isKeyPressed && keyCode === DOWN_ARROW) {
playerY = playerY + 5;
}
if (isKeyPressed && keyCode === RIGHT_ARROW) {
playerX = playerX + 5;
}
if (isKeyPressed && keyCode === LEFT_ARROW) {
playerX = playerX - 5;
}
fill(0, 255, 0);
ellipse(playerX, playerY, 50, 50);
fill(255, 0, 0);
ellipse(aiX, aiY, 50, 50);
let offsetX = playerX - aiX;
let offsetY = playerY - aiY;
// pythagorean theorem: given a right triangle, the length of the
// hypotenuse is the square root of side A squared plus side B squared
let length = sqrt(offsetX * offsetX + offsetY * offsetY);
offsetX = offsetX / length;
offsetY = offsetY / length;
// The total distance moved when incrementing will now be 1
aiX += offsetX;
aiY += offsetY;
if (dist(playerX, playerY, aiX, aiY) < 50) {
console.log("GAMEOVER");
reset();
}
}
function reset() {
playerX = 200;
playerY = 200;
aiX = 200;
aiY = 15;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
However, since you asked about the trigonometry, here's how the original example works:
The atan2 function, which implements the inverse tangent function (a.k.a arctangent), takes the vertical offset between two points and the horizontal offset between the two points, and returns the angle of the line between the two points relative to some reference direction (the positive X direction). This is the inverse of the tangent function which takes an angle, and returns the ratio of the opposite to the adjacent sides of a right triangle with that angle. The reason for using atan2 which takes the two offsets separately vs. the atan function which takes the ratio as a single value, is that atan will not return correct angles when the x offset is negative.
Now, once you have the angle of a line from the ai position to the player position, you can use other trigonometric functions to convert back to offsets: namely sine and cosine (i.e. sin and cos). The sine function takes an angle and returns the ratio of the opposite side of a right triangle with that angle and its hypotenuse. The cosine function takes an angle and returns the ratio of the adjacent side of a right triangle with that angle and its hypotenuse. So if you use sin(angle) as the vertical offset to move by, and cos(angle) as the horizontal offset, then you are moving in the direction of angle by an offset of 1.
To better understand all of this I recommend you watch the first few chapters of The Nature of Code by Daniel Shiffman.
I also think this p5.js sketch by spencer eastcott is helpful for visualizing these concepts.

html canvas clip but with an image

I have been working with html canvas compositing trying to clip a pattern with a mask.
The main issue that I have is that the mask I have comes from an svg with transparencies within the outer most border. I want the entire inside from the outer most border to be filled with the pattern.
Take this SVG for example you can see that there is a single pixel border, then some transparency, and then an opaque red inner blob. The compositing I have done works as the documentation says it should, the single pixel border and the red inner portion pick up the pattern that I want to mask into this shape. The problem is that I want to mask the entire innards starting from the single pixel border.
This is where I think clip might help. But it seems clip only works with manually drawn paths, not paths from an svg (at least that I am aware of).
Is there a way to accomplish what I am trying to do?
Regards,
James
The Path2D constructor accepts an SVG path data argument, that it will parse as the d attribute of an SVG <path> element.
You can then use this Path2D object with the clip() method:
(async () => {
// fetch the svg's path-data
const markup = await fetch("https://upload.wikimedia.org/wikipedia/commons/7/76/Autism_spectrum_infinity_awareness_symbol.svg").then(resp => resp.ok && resp.text());
const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
const pathData = doc.querySelector("[d]").getAttribute("d");
// build our Path2D object and use it
const path = new Path2D(pathData);
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.clip(path);
// draw something that will get clipped
const rad = 30;
for(let y = 0; y < canvas.height; y += rad * 2 ) {
for(let x = 0; x < canvas.width; x += rad * 2 ) {
ctx.moveTo(x+rad, y);
ctx.arc(x, y, rad, 0, Math.PI*2);
}
}
ctx.fillStyle = "red";
ctx.fill();
})().catch(console.error);
<canvas width="792" height="612"></canvas>
If you need to transform this path-data (e.g scale, or rotate), then you can create a second Path2D object, and use its .addPath(path, matrix) method to do so:
// same as above, but smaller
(async () => {
const markup = await fetch("https://upload.wikimedia.org/wikipedia/commons/7/76/Autism_spectrum_infinity_awareness_symbol.svg").then(resp => resp.ok && resp.text());
const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
const pathData = doc.querySelector("[d]").getAttribute("d");
const originalPath = new Path2D(pathData);
const path = new Path2D();
// scale by 0.5
path.addPath(originalPath, { a: 0.5, d: 0.5 });
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.clip(path);
// draw something that will get clipped
const rad = 15;
for(let y = 0; y < canvas.height; y += rad * 2 ) {
for(let x = 0; x < canvas.width; x += rad * 2 ) {
ctx.moveTo(x+rad, y);
ctx.arc(x, y, rad, 0, Math.PI*2);
}
}
ctx.fillStyle = "red";
ctx.fill();
})().catch(console.error);
<canvas width="396" height="306"></canvas>

why is that gl.clear(gl.COLOR_BUFFER_BIT) and requestAnimationFrame will clear all the primitives I drew before

Hi guys I been leanring WebGL and trying to make a Tetris game out of it.
I have a couple of questions I'd like to ask:
For this game I wanted to first draw the grid as the background. However I noticed that after I drew the line, if I use gl.clear(gl.COLOR_BUFFER_BIT ); after, it will clear all the lines I drew before. I understand that gl.clear(gl.COLOR_BUFFER_BIT ); is about clearing the color buffer (and you probably will ask why I would want to do that. Just bear with me. ). Then I tried use gl.uniform4f( uColor, 0, 0, 0, 1); to send the color again to the fragment shader but it doesn't help.
The snippet is like this
window.onload = function(){
getGLContext();
initShaders();
drawLines( 0, 0, 400,400 );
gl.clear(gl.COLOR_BUFFER_BIT );
gl.uniform4f( uColor, 0, 0, 0, 1);
}
For the game I need the grid as background and I need requestAnimationFrame for the game loop and will render Tetrominos inside the loop. Therefore after drawing the line I used this draw() to draw other Tetrominos. However it removes the line I drew before. And when I comment out gl.clear(gl.COLOR_BUFFER_BIT ); inside draw(), it will remove the line along with background color.
function draw() {
gl.clear(gl.COLOR_BUFFER_BIT );
gl.drawArrays(gl.TRIANGLES, 0, index*6);
requestAnimationFrame(draw);
}
Here is the demo: https://codepen.io/zhenghaohe/pen/LqxpjB
Hope you could answer these two questions. Thanks!
This is generally the way WebGL works.
WebGL is just draws into a rectangle of pixels. There is no memory of primitives. There is no structure. There is just code and the resulting canvas which is an rectangle of pixels.
Most WebGL programs/pages clear the entire canvas every frame and redraw 100% of the things they want to show every time they draw. For tetris the general code might be something like
function render() {
clear the canvas
draw the grid
draw all the stable pieces
draw the current piece
draw the next piece
draw the effects
draw the score
}
Any knowledge of primitives or other structure is entirely up to your code.
If you want the grid lines to be static then either set a static background with CSS or use another canvas
Using a background:
const gl = document.querySelector('#c').getContext('webgl');
function render(time) {
time *= 0.001;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
drawBlocks(gl, time);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// --- below this line not important to the answer
function drawBlocks(gl, time) {
gl.enable(gl.SCISSOR_TEST);
const numBlocks = 5;
for (let i = 0; i < numBlocks; ++i) {
const u = i / numBlocks;
gl.clearColor(i / 5, i / 2 % 1, i / 3 % 1, 1);
const x = 150 + Math.sin(time + u * Math.PI * 2) * 130;
const y = 75 + Math.cos(time + u * Math.PI * 2) * 55;
gl.scissor(x, y, 20, 20);
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.disable(gl.SCISSOR_TEST);
}
#c {
background-image: url(https://i.imgur.com/ZCfccZh.png);
}
<canvas id="c"></canvas>
Using 2 canvases
// this is the context for the back canvas. It could also be webgl
// using a 2D context just to make the sample simpler
const ctx = document.querySelector('#back').getContext('2d');
drawGrid(ctx);
// this is the context for the front canvas
const gl = document.querySelector('#front').getContext('webgl');
function render(time) {
time *= 0.001;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
drawBlocks(gl, time);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// --- below this line not important to the answer
function drawBlocks(gl, time) {
gl.enable(gl.SCISSOR_TEST);
const numBlocks = 5;
for (let i = 0; i < numBlocks; ++i) {
const u = i / numBlocks;
gl.clearColor(i / 5, i / 2 % 1, i / 3 % 1, 1);
const x = 150 + Math.sin(time + u * Math.PI * 2) * 130;
const y = 75 + Math.cos(time + u * Math.PI * 2) * 55;
gl.scissor(x, y, 20, 20);
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.disable(gl.SCISSOR_TEST);
}
function drawGrid(ctx) {
// draw grid
ctx.translate(-10.5, -5.5);
ctx.beginPath();
for (let i = 0; i < 330; i += 20) {
ctx.moveTo(0, i);
ctx.lineTo(330, i);
ctx.moveTo(i, 0);
ctx.lineTo(i, 300);
}
ctx.strokeStyle = "blue";
ctx.stroke();
}
#container {
position: relative; /* required so we can position child elements */
}
#front {
position: absolute;
left: 0;
top: 0;
}
<div id="container">
<canvas id="back"></canvas>
<canvas id="front"></canvas>
</div>
As for why it clears even if you didn't call clear that's because that's whqt the spec says it's supposed to do
See: Why WebGL 'clear' draw to front buffer?

Drawing rectangle underneath PIXI.Text

In a word game I am trying to draw score as white numbers above a blue (or red) rectangle:
For example, in the above screenshot it is the number "13".
Here is my entire class Score.js (with currently hardcoded WIDTH and HEIGHT):
"use strict";
function Score(color) {
PIXI.Container.call(this);
this.interactive = false;
this.buttonMode = false;
this.visible = false;
this.bgGraphics = new PIXI.Graphics();
this.bgGraphics.beginFill(color, 1);
this.bgGraphics.drawRect(0, 0, Score.WIDTH, Score.HEIGHT);
this.bgGraphics.endFill();
this.addChild(this.bgGraphics);
this.text = new PIXI.Text('XXX', {font: '20px Arial', fill: 0xFFFFFF});
this.text.x = 1;
this.text.y = 1;
this.addChild(this.text);
}
Score.prototype = Object.create(PIXI.Container.prototype);
Score.prototype.constructor = Score;
Score.WIDTH = 36;
Score.HEIGHT = 24;
Score.prototype.setText = function(str) {
this.text.text = str;
}
I wonder, how to modify my setText() function, so that a new rectangle is drawn on each call - as a bounding rectangle for the str argument?
I have looked at the PIXI.Text.getBounds() method, but it returns a Matrix and not a Rectangle...
I think you can just use this.text.width. This has historically had some bugs associated with it, but it should be working right in the latest version.

Phaser: remove a circle previously drawn with drawCircle

In Phaser (2.4.x), I'm drawing a circle around a sprite when it is dragged:
function dragStart(sprite, pointer, dragX, dragY) {
var graphics = game.add.graphics(0, 0);
graphics.lineStyle(6, 0x909090, 0.3);
graphics.drawCircle(dragX, dragY, 200);
}
That works fine, but now I need to remove the circle when the drag ends, and I can't figure that part out:
function dragStop() {
// ?
}
Is it possible to remove graphics? Is there a better or simpler option to draw a circle and remove it later?
You could kill() the object
But be carefull with the scope of the var you want kill (you are defining it inside the function).
Or you could just create the graphic and then show or hide depending of your event (drag)
I leave you a very simple example with both solutions:
var game = new Phaser.Game(500, 500, Phaser.AUTO, 'game');
var mainState = {
create:function(){
var graphics = game.add.graphics(0, 0);
graphics.lineStyle(6, 0x909090, 0.3);
graphics.drawCircle(game.world.centerX+100,game.world.centerY+100, 200);
console.log(graphics);
setTimeout(function(){
graphics.kill();
},2000);
this.graphics2 = game.add.graphics(0, 0);
this.graphics2.lineStyle(6, 0xff0000, 1);
this.graphics2.drawCircle(game.world.centerX-100,game.world.centerY-100, 200);
this.graphics2.visible = false;
this.show_later = game.time.now + 2000;
this.hide_again = game.time.now + 4000;
},
update:function(){
if(this.show_later < game.time.now){
this.graphics2.visible = false;
}
if(this.hide_again < game.time.now){
this.graphics2.visible = true;
}
},
};
game.state.add('main', mainState);
game.state.start('main');
<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/2.4.4/phaser.min.js"></script>
<div id="game"></div>

Resources