How to close the path - paperjs

I have a shape that is created using a 'for' loop. The first path has no anchors and therefore is pointy. I'm not sure if that is because it is not closed? Joined? Sketch
var circle= new Path.Circle({
radius: 100,
position: [200,200]
})
splat= new Path()
splat.fillColor= 'pink'
var count= 5
var length= circle.length
for(var i = 0; i <= count; i++){
var offset= i / count * length
const normal = i === 0 || i === count
? new Point(0, 0)
: circle.getNormalAt(offset) * (Math.random() * 50);
const point = circle.getPointAt(offset).add(i % 2 == 0 ? normal
: -normal);
console.log(point)
splat.add(point)
splat.smooth({ type: 'catmull-rom', factor: 0.5 });
}
splat.fullySelected= true
splat.closed= true
Thanks in advance again.

You just need to call the smooth() function once and you have to call it after setting the path to closed.
Then another thing that prevent the smoothing to work properly is that your first and last points of the path are the same, remove the last point and it will work as expected.
Sketch

Related

Qt3D - Import gltf and play animation

I'm currently struggling to play the animation of a gltf-file with Qt3D/QML.
Model I want to use:
https://sketchfab.com/3d-models/plane-cedc8a07370747f7b0d14400cdf2faf9
Code I have so far:
Entity {
id: root
Transform {
id: planeTransform
scale: 0.1
}
components: [
planeClip,
planeTransform,
planeScene
]
SceneLoader{
id:planeScene
source: "qrc:/Modells/3DModelle/plane/scene.gltf"
}
AnimationClipLoader{
id: planeClipLoader
source: "qrc:/Modells/3DModelle/plane/scene.gltf"
}
ClipAnimator{
id: planeClip
clip: planeClipLoader
channelMapper: ChannelMapper {
mappings: [ ] // Dont know where to get this
}
loops: Animation.Infinite
running: true
}
}
I use the SceneLoader to check if the gltf can be even loaded (it works, I can see the plane).
But I'm struggling to start the animation because I don't know where I get the ChannelMapper from.
When I open the file with 3DViewer on Windows, it plays the Animation correctly, so I suppose it should work without much insider knowledge of the file itself.
Anyone here familiar with playing gltf animations?
Thanks for the help!
To do this you need to traverse the tree of Entity(s) build by SceneLoader and find the appropriate component to initialize ClipAnimator with.
It's going to look something like this:
SceneLoader {
id: planeScene
source: "qrc:/Modells/3DModelle/plane/scene.gltf"
onStatusChanged: {
console.log("SceneLoader status=" + status);
if (status != SceneLoader.Ready)
return;
// retrieve a list of all the Entity(s) in the scene
var entityNames = planeScene.entityNames();
console.log("SceneLoader: entityNames=" + entityNames);
// traverse the Entity tree
for (var i = 0; i < entityNames.length; ++i) {
var entityName = entityNames[i];
var entityVar = planeScene.entity(entityName);
console.log("SceneLoader: entity=" + entityName + " components.length=" + entityVar.components.length);
// iterate on the components of this Entity
for (var j = 0; j < entityVar.components.length; ++j) {
var cmp = entityVar.components[j];
if (!cmp)
continue;
var cmp_class = cmp.toString();
console.log("SceneLoader: -> " + cmp_class);
// compare the type of this component to whatever type
// you are looking for. On this silly example, its QPhongMaterial:
if (cmp_class.indexOf("QPhongMaterial") >= 0) {
// initialize ClipAnimator with this data
}
}
}
}
}
I'm sharing the generic procedure here as I don't have a way to test this right now and figure out the data type you need to initialize ClipAnimator correctly.

Google Maps API complaining in IE11 about polyfill Array.from()

I'm trying to figure out an issue with Google Maps v3 and a polyfill we use for non-ES6 browsers (IE11 for example). The error we get is:
This site overrides Array.from() with an implementation that doesn't support iterables, which could cause Google Maps JavaScript API v3 to not work correctly.
The polyfill is: ( from https://vanillajstoolkit.com/polyfills/arrayfrom/ )
if (!Array.from) {
Array.from = (function () {
var toStr = Object.prototype.toString;
var isCallable = function (fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var toInteger = function (value) {
var number = Number(value);
if (isNaN(number)) { return 0; }
if (number === 0 || !isFinite(number)) { return number; }
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function (value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike/*, mapFn, thisArg */) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError('Array.from requires an array-like object - not null or undefined');
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method
// of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
}
This works fine on other pages - but for some reason Google Maps seems to have an issue with it!
Even more frustratingly, is that it then breaks one of my other plugins (a lazy load script), which works fine until the Google map stuff is loaded
Any ideas on what its moaning about, and how to fix it?
If you have IE11 or a VM, you can test it at: https://www.chambresdhotes.org/Detailed/1768.html (click on the map at the bottom of the page, and this will load the Google Map - but then you get this annoying error, and it breaks the lazyload scrolling after)
Thanks!

How can I stop and start view.onFrame

I'm pretty new to paperjs. My animation is working for this I use the following javascript:
view.onFrame = function () {
drawYellowBlock();
}
The function drawYellowBlock draws a yellow block but this animated. When the animation is done I want to stop view.onFrame because I have the feeling it is unnecessary to keep it running while nothing is happening any more. Then when a button is clicked I should be able to activate the onFrame again.
Is this possible and necessary?
So I want my draw function to be something like this:
var scale = 0;
function drawYellowBlock() {
scale = scale + 0.1
//animate block
if(scale < = 1){
//make block grow
}
else{
//stop onFrame
}
$('button').click(function(){
scale = 0;
//start onFrame and Animation
});
You can simply set up a flag used in onFrame method to check if you should animate or not.
Here is a sketch demonstrating the solution.
// Draw the item with a small initial scale.
var item = new Path.Rectangle({
from: view.center - 100,
to: view.center + 100,
fillColor: 'orange',
applyMatrix: false
});
item.scaling = 0.1;
// Draw instructions.
new PointText({
content: 'Press space to start animation',
point: view.center + [0, -80],
justification: 'center'
});
// Create a flag that we will use to know wether we should animate or not.
var animating = false;
// On space key pressed...
function onKeyDown(event) {
if (event.key === 'space') {
// ...start animation.
animating = true;
}
}
// On frame...
function onFrame() {
// ...if animation has started...
if (animating) {
// ...scale up the item.
item.scaling += 0.05;
// When item is totally scaled up...
if (item.scaling.x >= 1) {
// ...stop animation.
animating = false;
}
}
}
You can do something like this
function draw () {
drawYellowBlock();
view.onFrame = undefined
}
view.onFrame = draw
function onclickHandler(){
view.onFrame = draw
}
just remove the function reference from the onFrame handler once its done and append the function back once the button is clicked

Particles.js : limit number of particles

I can't find if it is possible to limit the total number of particles.
Is there any way of doing this?
Particles.js Github
You can modify particles.js (row 750) adding an additional check in the push function:
/* ---------- pJS functions - modes events ------------ */
pJS.fn.modes.pushParticles = function(nb, pos){
pJS.tmp.pushing = true;
if(pJS.particles.array.length<140){
for(var i = 0; i < nb; i++){
pJS.particles.array.push(
new pJS.fn.particle(
pJS.particles.color,
pJS.particles.opacity.value,
{
'x': pos ? pos.pos_x : Math.random() * pJS.canvas.w,
'y': pos ? pos.pos_y : Math.random() * pJS.canvas.h
}
)
)
if(i == nb-1){
if(!pJS.particles.move.enable){
pJS.fn.particlesDraw();
}
pJS.tmp.pushing = false;
}
}
}
};
I used 140 as maximum number since it is a good value in order to not lose performance. Obviously you can modify it as needed.
I added a little on #Nicola Zaltron's solution making it responsive to the screen size by making the limit of particles based on the screen width and by doing that on smaller screens it wont look bad and it wont effect the performance.
/* ---------- pJS functions - modes events ------------ */
pJS.fn.modes.pushParticles = function(nb, pos){
pJS.tmp.pushing = true;
var limitNumOfParticles = Math.floor(pJS.canvas.el.width / 20);
if(pJS.particles.array.length < limitNumOfParticles){
// console.log("limit: ", limitNumOfParticles);
// console.log("array: ", pJS.particles.array.length);
for(var i = 0; i < nb; i++){
pJS.particles.array.push(
new pJS.fn.particle(
pJS.particles.color,
pJS.particles.opacity.value,
{
'x': pos ? pos.pos_x : Math.random() * pJS.canvas.w,
'y': pos ? pos.pos_y : Math.random() * pJS.canvas.h
}
)
)
if(i == nb-1){
if(!pJS.particles.move.enable){
pJS.fn.particlesDraw();
}
pJS.tmp.pushing = false;
}
}
}
};
A solution can be(with javascript):
// Get total particles
let particles = window['pJSDom'][0].pJS.particles.array.length
// Here set limit value
if(particles == 25){
// Get particles array
let array = window['pJSDom'][0].pJS.particles.array
// Remove (starting from zero) 16 particles starting from 5
array.splice(5,15)
// Set new particles array
window['pJSDom'][0].pJS.particles.array = array
}
I think that there is no way to limit the number of particles onscreen, so I changed the properties of the onHover and onClick so it won't add other particles. Solved my problem...
EDIT
This was previously marked as answer because at the time it solved my problem, but is not a proper answer.
No longer marked as answer, since apparently #Nicola Zaltron answer works !
i've found a good solution!
This is the simple code:
const maxParticles = 60;
particlesJS.load('particles', './assets/particlesjs.json', () => {
const pJSIndex = 0;
const pJS = pJSDom[pJSIndex].pJS;
pJS.particles.array.splice(0, pJS.particles.array.length - maxParticles);
pJS.interactivity.el.addEventListener('click', () => {
pJS.particles.array.splice(0, pJS.particles.array.length - maxParticles);
});
});
When initialising particlesJS, change the value of number.value to limit number of particles
particlesJS("particles-js", {
"particles": {
"number": {
"value": <change this to limit number of particles>,
"density": {
"enable": true,
"value_area": 800
}
},
......
}
See it in action : http://codepen.io/anon/pen/ggoRXy

Click Listeners in Loop - Array and Closure

I realise I'm treading on thin ice opening another closure issue, but I have searched and can't find an answer to my issue. I have a Google Maps API v3 page which generates two maps from one block of code - a small map centered on the user's current location and a larger map showing the whole area with the user's location marked where it is, center or not. On top of the map is a rectangle layer consisting of 14 rectangles. In order to generate the two maps, I have had to put the rectangles in a 2 dimensional array, rectangles[1] for 'map', and rectangles[2] for 'map2':
var rectangles = [0,1,2,3,4,5,6,7,8,9,10,11,12,13];
rectangles[1][0]=new google.maps.Rectangle({
bounds:new google.maps.LatLngBounds(new google.maps.LatLng(a, b), new google.maps.LatLng(x, y)),
map:map,
fillColor:'red',
fillOpacity: 0.3,
strokeOpacity: 0,
url: 'http://example.com',
clickable: true
});
rectangles[2][0]=new google.maps.Rectangle({
bounds:new google.maps.LatLngBounds(new google.maps.LatLng(a, b), new google.maps.LatLng(x, y)),
map:map2,
fillColor:'red',
fillOpacity: 0.3,
strokeOpacity: 0,
url: 'http://example.com',
clickable: true
});
...and so on. It all works fine and the two maps are displayed and the geolocation works. Now I want to add a click listener for each rectangle but I'm not sure who to reference the array. This is what I have now:
for ( i = 0; i < rectangles[1].length; i++ ){
google.maps.event.addListener(rectangles[1][i], 'click', function() {
window.location.href = this.url;
});
}
for ( x = 0; x < rectangles[2].length; x++ ){
google.maps.event.addListener(rectangles[2][x], 'click', function() {
window.location.href = this.url;
});
}
Which obviously won't work. I have seen various solutions to the closure issue, but I'm not sure I'm even heading in the right direction in referencing the two arrays of rectangles - or if I even need to define two different sets of click listeners. I'd be really grateful if someone could point me in the right direction - and sorry if this is just going over old ground that appears obvious. There's always a new learner coming along who is trying hard to catch up.
Thanks.
//First, set up `rectangles` as an array containing two arrays.
var rectangles = [];
rectangles[0] = [];
rectangles[1] = [];
//As `google.maps.Rectangle` doesn't accept a `url` option,
//its url needs to be defined separately from the rectangle itself,
//but in such a way that the two are associated with each other.
//For this we can use a javascript plain object.
rectangles[0][0] = {
rect: new google.maps.Rectangle({
bounds: new google.maps.LatLngBounds(new google.maps.LatLng(a, b), new google.maps.LatLng(x, y)),
map: map,
fillColor: 'red',
fillOpacity: 0.3,
strokeOpacity: 0,
clickable: true
}),
url: 'http://example.com'
};
rectangles[1][0] = new google.maps.Rectangle({
...
});
rectangles[0][1] = new google.maps.Rectangle({
...
});
rectangles[1][1] = new google.maps.Rectangle({
...
});
rectangles[0][2] = new google.maps.Rectangle({
...
});
rectangles[1][2] = new google.maps.Rectangle({
...
});
//Now to attach the click listeners.
//First we define a function that adds a click listener.
//By doing this in its own function, a closure is formed,
//trapping the formal variable `rectObj` and making `rectObj.url`
//accessible to the listener when it is called in response to future clicks.
function addClickListener(rectObj) {
google.maps.event.addListener(rectObj.rect, 'click', function() {
window.location.href = rectObj.url;
});
}
//Now, we can loop through the `rectangles` arrays, adding listeners.
for ( i = 0; i < 2; i++ ) {
for ( j = 0; j < 14; j++ ) {
if(rectangles[i][j]) {//safety
addClickListener(rectangles[i][j]);
}
}
}

Resources