How to create a hole on a Path in Paper.js? - paperjs

I want to get a result like this while the white circle is being actually a punch:
However, I'm getting the following result when I follow the boolean operation example:
// this works okay
var via = outer.exclude(hole)
project.activeLayer.addChild(via)
// this works weird
var drilledY = y.exclude(hole)
project.activeLayer.addChild(drilledY)
Here the only problem seems to be creating the hole inside the Path. How can I create a hole in the path?

I don't think you can get the result you want using Path.Line.
Punching through implies that you want to remove some internal area, which an open Path such as Path.Line lacks.
So what you can do is the following:
Replace those thick Lines with Path.Rectangles.
unite the 2 rectangles, to get your cross, so you have one Path to operate on.
Use subtract instead of exclude to "punch through".
Here's an example:
var x = new paper.Path.Rectangle({
from: [100, 100],
to: [120, 200],
fillColor: 'red',
strokeWidth: 1
});
var y = x.clone().rotate(90).set({ fillColor: 'blue' })
// Unite x/y to get a single path.
var cross = y.unite(x)
// Remove x,y we no longer need them, we got the cross.
x.remove()
y.remove()
var hole = new paper.Path.Circle({
center:[110, 150],
radius: 6,
strokeColor: 'red',
fillColor: 'red'
})
// Subtract (instead of exclude), to "punch through".
var drilled = cross.subtract(hole)
// Remove hole/cross, we no longer need them.
hole.remove()
cross.remove()
console.log(drilled)
and here's a Sketch.
If you don't want to unite your shapes, you can still loop through them
and subtract the hole from them, just remember to use closed Paths.

Related

How can I merge geometries in A-Frame without losing material information?

I have a large set of block objects using a custom geometry, that I am hoping to merge into a smaller number of larger geometries, as I believe this will reduce rendering costs.
I have been following guidance here: https://aframe.io/docs/1.2.0/introduction/best-practices.html#performance which has led me to the geometry-merger component here:
https://github.com/supermedium/superframe/tree/master/components/geometry-merger/
The A-Frame docs say:
"You can use geometry-merger and then make use a three.js material with vertex colors enabled. three.js geometries keep data such as color, uvs per vertex."
The geometry-merger component also says:
"Useful if using vertex or face coloring as individual geometries' colors can still be manipulated individually since this component keeps a faceIndex and vertexIndex."
However I have a couple of problems.
If I set vertexColors on my material (as suggested by the A-Frame docs), then this ruins the appearance of my blocks.
Whether or not I set vertexColors on my material, all material information seems to be lost when the geometries are merged, and everything just ends up white.
See this glitch for a demonstration of both problems.
https://tundra-mercurial-garden.glitch.me/
My suspicion is that the A-Frame geometry-merger component just won't do what I need here, and I need to implement something myself using the underlying three.js functions.
Is that right, or is there a way that I could make this work using geometry-merger?
For the vertexColors to work, you need to have your vertices coloured :)
More specifically - the BufferGeometry expects an array of rgb values for each vertex - which will be used as color for the material.
In this bit of code:
var geometry = new THREE.BoxGeometry();
var mat = new THREE.MeshStandardMaterial({color: 0xffffff, vertexColors: THREE.FaceColors});
var mesh = new THREE.Mesh(geometry, mat);
The mesh will be be black unless the geometry contains information about the vertex colors:
// create a color attribute in the geometry
geometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(vertices_count), 3));
// grab the array
const colors = this.geometry.attributes.color.array;
// fill the array with rgb values
const faceColor = new THREE.Color(color_hex);
for (var i = 0; i < vertices_count / 3; i += 3) {
colors[i + 0] = faceColor.r; // lol +0
colors[i + 1] = faceColor.g;
colors[i + 2] = faceColor.b;
}
// tell the geometry to update the color attribute
geometry.attributes.color.needsUpdate = true;
I can't make the buffer-geometry-merger component work for some reason, but It's core seems to be valid:
AFRAME.registerComponent("merger", {
init: function() {
// replace with an event where all child entities are ready
setTimeout(this.mergeChildren.bind(this), 500);
},
mergeChildren: function() {
const geometries = [];
// traverse the child and store all geometries.
this.el.object3D.traverse(node => {
if (node.type === "Mesh") {
const geometry = node.geometry.clone();
geometry.applyMatrix4(node.parent.matrix);
geometries.push(geometry)
// dispose the merged meshes
node.parent.remove(node);
node.geometry.dispose();
node.material.dispose();
}
});
// create a mesh from the "merged" geometry
const mergedGeo = THREE.BufferGeometryUtils.mergeBufferGeometries(geometries);
const mergedMaterial = new THREE.MeshStandardMaterial({color: 0xffffff, roughness: 0.3, vertexColors: THREE.FaceColors});
const mergedMesh = new THREE.Mesh(mergedGeo, mergedMaterial);
this.el.object3D.add(mergedMesh)
}
})
You can check it out in this glitch. There is also an example on using the vertex colors here (source).
I agree it sounds like you need to consider other solutions. Here are two different instances of instancing with A-Frame:
https://github.com/takahirox/aframe-instancing
https://github.com/EX3D/aframe-InstancedMesh
Neither are perfect or even fully finished, but can hopefully get you started as a guide.
Although my original question was about geometry merging, I now believe that Instanced Meshes were a better solution in this case.
Based on this suggestion I implemented this new A-Frame Component:
https://github.com/diarmidmackenzie/instanced-mesh
This glitch shows the scene from the original glitch being rendered with just 19 calls using this component. That compares pretty well with > 200 calls that would have been required if every object were rendered individually.
https://dull-stump-psychology.glitch.me/
A key limitation is that I was not able to use a single mesh for all the different block colors, but had to use one mesh per color (7 meshes total).
InstancedMesh can support different colored elements, but each element must have a single color, whereas the elements in this scene had 2 colors each (black frame + face color).

How can I generate a mask on a solid or create a custom (complex) drawing on that solid to Adobe After Effects only via scripting

I'm making an After Effects script that generates simple shapes & animations for kids, and I'm trying to avoid importing vector shapes from Illustrator to After Effects to animate them. And that is working perfectly with simple shapes such as squares and circles.
Is there any solution for generating complex shapes inside the Extendscript Toolkit, a pure code with no imports or locating some .txt file, just by setting the vertices, position and color of the shape and applies it to a new solid as a mask by running the script inside of After Effects?
If I wanted to do it manually, I will add a new solid, copy the first path from Illustrator, and back to after effects to paste it on that solid,then I'll add another solid, back to illustrator, copy another path, back to after effect, paste it on solid 2, and I'll repeat the process till the final result appears.
I want to end this switching between software 1 and 2 and save the drawing as an array of [vertices], [in-tangents], and [out-tangents] and call it whenever I want!
Running the script
The Result
I've done it like this, it can be used for import any kind of footage
var path = "File Path";
var input = new ImportOptinputns(File(path));
if (input.canImportAs(ImportAsType.FOOTAGE));
input.importAs = ImportAsType.FOOTAGE;
Or if you want to import an image sequence you can do it like this
// or if your footage is an image sequence
input.sequence = true;
input.forceAlphabetical = true;
imageSequence = app.project.importFile(input);
imageSequence.name = 'My automatically imported foorage";
theComp = app.project.activeItem; //import in to currently selected composition
theComp.layers.add(imageSequence);
I know how to create simple vector objects via script but I'm not sure if its work for you as you want it.
An example of two group rectangle
var shapeLayer = newComp.layers.addShape(); // adding shape layer
shapeLayer.name = "bannerLayer"; // name the shape layer
var shapeGroup1 = shapeLayer.property("Contents").addProperty("ADBE Vector Group"); / creating a group1
shapeGroup1.name = "Banner"; //name the group1
myRect= shapeGroup1.property("Contents").addProperty("ADBE Vector Shape - Rect"); // adding rectangle to the group1
Another example of a more complex shape, a triangle add to an existing shape layer, you can use this code as a base and create more complex shapes.
var shapeLayer = newComp.layers.addShape(); // adding shape layer
shapeLayer.name = "bannerLayer"; // name the shape layer
var shapeGroup1 = shapeLayer.property("Contents").addProperty("ADBE Vector Group"); // creating a group1
shapeGroup1.name = "Banner"; //name the group1
myRect = shapeGroup1.property("Contents").addProperty("ADBE Vector Shape - Rect"); // adding rectangle to the group1
// construct a Shape object that forms a triangle
var myTriShape = new Shape();
myTriShape.vertices = [[-50,50], [50,50], [0,100]];
myTriShape.closed = true;
// add a Path group to our existing shape layer
myTriGroup = shapeLayer.property("Contents").addProperty("ADBE Vector Group"); // adding rectangle to the group1
myTriGroup.name = "Triangle";
myTri = myTriGroup.property("Contents").addProperty("ADBE Vector Shape - Group");
// set the Path property in the group to our triangle shape
myTri.property("Path").setValue(myTriShape);
you can find more information on this page. I googled it myself.
Check this link https://forums.creativecow.net/docs/forums/post.php?forumid=2&postid=1119306&univpostid=1119306&pview=t

How to select a single Curve in a Path in Paper.js?

I need to distinctly select a single Curve of a clicked Path, how can I do that?
For example, in this sketch we can select a whole path when clicked on it:
Currently I can detect the curve (not sure if it is the appropriate approach, anyway):
..onMouseDown = (event) ~>
hit = scope.project.hitTest event.point
if hit?item
# select only that specific segment
curves = hit.item.getCurves!
nearest = null
dist = null
for i, curve of curves
_dist = curve.getNearestPoint(event.point).getDistance(event.point)
if _dist < dist or not nearest?
nearest = i
dist = _dist
selected-curve = curves[nearest]
..selected = yes
But whole path is selected anyway:
What I want to achieve is something like this:
There is an easier way to achieve what you want.
You can know if hit was on a curve by checking its location property.
If it is set, you can easily get the curve points and manually draw your selection.
Here is a sketch demonstrating it.
var myline = new Path(new Point(100, 100));
myline.strokeColor = 'red';
myline.strokeWidth = 6;
myline.add(new Point(200, 100));
myline.add(new Point(260, 170));
myline.add(new Point(360, 170));
myline.add(new Point(420, 250));
function onMouseDown(event) {
hit = paper.project.hitTest(event.point);
// check if hit is on curve
if (hit && hit.location) {
// get curve
var curve = hit.location.curve;
// draw selection
var selection = new Group(
new Path.Line({
from: curve.point1,
to: curve.point2,
strokeColor: 'blue',
strokeWidth: 3
}),
new Path.Rectangle({
from: curve.point1 - 5,
to: curve.point1 + 5,
fillColor: 'blue'
}),
new Path.Rectangle({
from: curve.point2 - 5,
to: curve.point2 + 5,
fillColor: 'blue'
})
);
// make it automatically be removed on next down event
selection.removeOnDown();
}
}
Update
As an alternative, to avoid messing up with the exported drawing, you can simply select the line instead of applying it a stroke style.
See this sketch.
var selection = new Path.Line({
from: curve.point1,
to: curve.point2,
selected: true
});
There is no built-in way to do what you'd like AFAIK.
You basically need to walk through the segments, construct a line, and see if the hit is on that particular line. The line cannot be transparent or it's not considered a hit which is why I give it color and width to match the visible line; it's also why it's deleted after the test.
Here's the sketch solution that implements a bit more around this:
function onMouseDown(event){
if (!myline.hitTest(event.point)) {
return
}
c1.remove()
c2.remove()
// there's a hit so this should find it
let p = event.point
let segs = myline.segments
for (let i = 1; i < segs.length; i++) {
let line = new Path.Line(segs[i - 1].point, segs[i].point)
line.strokeWidth = 6
line.strokeColor = 'black'
if (line.hitTest(p)) {
c1 = new Path.Circle(segs[i-1].point, 6)
c2 = new Path.Circle(segs[i].point, 6)
c1.fillColor = 'black'
c2.fillColor = 'black'
line.remove()
return
}
line.remove()
}
throw new Error("could not find hit")
}
Here's what I draw:

three js mirror not reflecting all meshes

Objective:
To simulate a reflective floor(like this) in three js.
Idea:
Make the floor translucent by setting opacity to 0.5.
Place a Mirror below it to reflect the meshes above it.
Expected Output:
To be able to see reflections of the house via the floor mirror.
Obtained Output:
Doesn't reflect the meshes which is part of the house.
Instead, reflects only the skybox and that too only in certain angles.
Screenshots:
Mirror reflecting skybox fully - http://prntscr.com/6yn52y
Mirror reflecting skybox partially - http://prntscr.com/6yn5f7
Mirror not reflecting anything - http://prntscr.com/6yn5qy
Questions:
Why aren't the other meshes of the house reflected through the mirror?
Why is the mirror not reflecting in certain orientations of the camera?
Code Attached:
.......
.......
function getReflectiveFloorMesh(floorMesh) {
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
floorMirror = new THREE.Mirror( renderer, firstPerson.camera,
{ clipBias: 0.003,
textureWidth: WIDTH,
textureHeight: HEIGHT,
color: 0x889999 } );
var mirrorMesh = floorMesh.clone();
mirrorMesh.position.y -= 10; // Placing the mirror just below the actual translucent floor; Fixme: To be tuned
mirrorMesh.material = floorMirror.material;
mirrorMesh.material.side = THREE.BackSide; // Fixme: Normals were flipped. How to decide on normals?
mirrorMesh.material.needsUpdate = true;
mirrorMesh.add(floorMirror);
return mirrorMesh;
}
function getSkybox() {
var urlPrefix = "/img/skybox/sunset/";
var urls = [urlPrefix + "px.png", urlPrefix + "nx.png",
urlPrefix + "py.png", urlPrefix + "ny.png",
urlPrefix + "pz.png", urlPrefix + "nz.png"];
var textureCube = THREE.ImageUtils.loadTextureCube(urls);
// init the cube shadder
var shader = THREE.ShaderLib["cube"];
shader.uniforms["tCube"].value = textureCube;
var material = new THREE.ShaderMaterial({
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
uniforms: shader.uniforms,
side: THREE.BackSide
});
// build the skybox Mesh
var skyboxMesh = new THREE.Mesh(new THREE.CubeGeometry(10000, 10000, 10000, 1, 1, 1, null, true), material);
return skyboxMesh;
}
function setupScene(model, floor) {
scene.add(model); // Adding the house which contains translucent floor
scene.add(getSkybox()); // Adding Skybox
scene.add(getReflectiveFloorMesh(floor)); // Adds mirror just below floor
scope.animate();
}
....
....
this.animate = function () {
// Render the mirrors
if(floorMirror)
floorMirror.render();
renderer.render(scene, firstPerson.camera);
};
You have to attach the mirror to the mesh before doing any transformation.
So the code would be:
floorMirror = new THREE.Mirror( ... );
var mirrorMesh = floorMesh.clone();
mirrorMesh.add(floorMirror); // attach first!
mirrorMesh.position.y -= 10;
...
But another problem here is that you are cloning mirrorMesh from floorMesh, which has already been (probably) transformed.
At creation, a mirror object has the same default transform matrix as a regular Mesh with plane geometry (which is by default 'vertical').
When you attach the mirror to a floor (or any horizontal mesh), the matrix doesn't match with the mesh one and that's why you don't see the reflections, or only from a certain angle.
So, always attach a mirror to a non-transformed plane mesh, before you apply your transformations (translations or rotations).
Idea:
"Make the floor translucent by setting opacity to 0.5.
Place a Mirror below it to reflect the meshes above it".
I suggest another way, make floor as solid add mirror on top and change the alpha of the mirror instead, I think you have issues with the translucent floor restricting the mirror projection through the alpha..
If you move the mirror over from under the translucent floor or into an empty scene with just a cube or sphere geometry attached basic material does it reflect as expected?
You may need 2 mirrors, one for the room assuming you want polished floor boards and one for outside general reflection

How to move raphael set?

I have a set of objects grouped with Raphael.set(). What I want to do is to move the whole set (change x and y coordinates) from one place to another.
How I can move the whole set as a single object?
What I found already is that when an .attr({X: newX, Y: newY}) is called every element from the set will be positioned on this coordinated which will result in piling all the elements in one place.
Edit: Refer to Rick Westera's answer as translate is now deprecated.
Use .translate(x, y), example:
var paper = Raphael('stage', 300, 300);
var set = paper.set();
set.push(paper.rect(0,0,30,50));
set.push(paper.circle(40,50,10));
set.push(paper.path("M 0 70 L 100 70"));
set.translate(100, 100);
http://jsfiddle.net/q4vUx/
Use transform('Tx,y') as translate is deprecated. For example:
var paper = Raphael('stage', 300, 300);
var set = paper.set();
set.push(paper.rect(0, 0, 100, 100));
set.push(paper.text(50, 50, "Foo"));
set.transform("T100,50");
Note that there are two types of translation:
'T100,50' will move the set 100px to the right and 50 down using the global axis.
't100,50' will do the same but using the set's local axis (ie. it depends on which way the set has been rotated).
This is what i'm using to reposition a set, in my case is a set of fonts returned by Paper.print() but i think it should work with any kind of set.
var glyphs = paper.print(0, 0, text, paper.getFont(font, 800), fontSize).hide();
glyphs.transform('...T' + [posx, posy] + 'R' + [angle, posx, posy]).show();
hope it helps.

Resources