Skip to content

Commit

Permalink
Clipping works
Browse files Browse the repository at this point in the history
  • Loading branch information
tomkennedy22 committed May 18, 2024
1 parent 12b53a4 commit 5d8ecdc
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 64 deletions.
2 changes: 1 addition & 1 deletion public/editor/stateStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ const createGallerySlice: StateCreator<CombinedState, [], [], CombinedState> = (
};
}),
gallerySectionConfigList,
gallerySize: "md",
gallerySize: "sm",
setGallerySize: (size: GallerySize) =>
set((state: CombinedState) => {
return { ...state, gallerySize: size };
Expand Down
125 changes: 92 additions & 33 deletions src/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import svgs from "./svgs.js";
import { FaceConfig, Overrides, RGB, HSL, HEX, FeatureInfo } from "./types";
// @ts-ignore
import paper from "paper-jsdom";
import { svgsIndex, svgsMetadata } from "./svgs-index.js";

const calcChildElement = (
const getChildElement = (
svg: SVGSVGElement,
insertPosition: "afterbegin" | "beforeend",
) => {
Expand All @@ -15,29 +16,64 @@ const calcChildElement = (
}
};

const getOuterStroke = (svgElement: SVGElement): paper.Path => {
// Initialize Paper.js project
paper.setup(document.createElement("canvas"));
const clipToParent = (
fullSvg: SVGSVGElement,
parentElement: any,
insertLocation: "afterbegin" | "beforeend",
) => {
let childElement = getChildElement(fullSvg, insertLocation) as SVGSVGElement;
let clippedItem = paper.project.importSVG(childElement);
fullSvg.removeChild(childElement);
let baseShape = new paper.CompoundPath({
children: findPathItems(parentElement.clone()),
});

// Import the SVGElement into Paper.js
const importedItem = paper.project.importSVG(svgElement);
let smallChildren = findPathItems(clippedItem);
let childGroup = new paper.Group();
for (let child of smallChildren) {
child.stroke = null;
child.strokeWidth = 0;

// Recursively find all path items in the imported item and its children
function findPathItems(item: paper.Item): paper.PathItem[] {
let paths: paper.PathItem[] = [];
let intersection = baseShape.intersect(child);

if (item instanceof paper.PathItem) {
paths.push(item);
}
intersection.fillColor = child.fillColor;
intersection.strokeColor = child.strokeColor;
intersection.strokeWidth = child.strokeWidth;

if (item.children) {
item.children.forEach((child: any) => {
paths = paths.concat(findPathItems(child));
});
}
childGroup.addChild(intersection);
}

let resultSVG = childGroup.exportSVG({ asString: true });
fullSvg.insertAdjacentHTML(insertLocation, resultSVG);

return paths;
childGroup.remove();

let newlyAddedElement = getChildElement(
fullSvg,
insertLocation,
) as SVGSVGElement;
newlyAddedElement.setAttribute("class", "clipToParent Output");
};

const findPathItems = (item: paper.Item): paper.PathItem[] => {
let paths: paper.PathItem[] = [];

if (item.children) {
item.children.forEach((child: any) => {
paths = paths.concat(findPathItems(child));
});
}
if (item instanceof paper.PathItem) {
paths.push(item);
}

return paths;
};

const getOuterStroke = (svgElement: SVGElement): string => {
paper.setup(document.createElement("canvas"));

const importedItem = paper.project.importSVG(svgElement);

const pathItems = findPathItems(importedItem);

Expand All @@ -55,13 +91,13 @@ const getOuterStroke = (svgElement: SVGElement): paper.Path => {
) as paper.Path;

unitedPath.strokeColor = new paper.Color("black");
unitedPath.strokeWidth = 4;
unitedPath.strokeWidth = 5;
unitedPath.fillColor = new paper.Color("transparent");

// Remove the imported item and its children from the project
importedItem.remove();

return unitedPath;
return unitedPath.exportSVG({ asString: true });
};

// Convert hex color to RGB
Expand Down Expand Up @@ -222,7 +258,8 @@ const getHairAccent = (hairColor: string): string => {
}
};

const addWrapper = (svgString: string) => `<g>${svgString}</g>`;
const addWrapper = (svgString: string, objectTitle?: string) =>
`<g class="${objectTitle}">${svgString}</g>`;

const addTransform = (element: SVGGraphicsElement, newTransform: string) => {
const oldTransform = element.getAttribute("transform");
Expand Down Expand Up @@ -346,6 +383,8 @@ const translate = (

// Defines the range of fat/skinny, relative to the original width of the default head.
const fatScale = (fatness: number) => 0.8 + 0.2 * fatness;

// Shotest/tallest range is only 0.85 to 1.15
// @ts-ignore
const heightScale = (height: number) => 0.85 + 0.3 * height;

Expand Down Expand Up @@ -414,6 +453,7 @@ const drawFeature = (
return;
}

// Dont let huge muscles be outside bounds of suit/referee jersey
if (
["suit", "suit2", "referee"].includes(face.jersey.id) &&
info.name == "body"
Expand Down Expand Up @@ -479,11 +519,13 @@ const drawFeature = (
let insertPosition: "afterbegin" | "beforeend" = info.placeBeginning
? "afterbegin"
: "beforeend";
// let whichChild: 'firstChild' | 'lastChild' = insertPosition == "beforebegin" ? 'firstChild' : 'lastChild';

for (let i = 0; i < info.positions.length; i++) {
svg.insertAdjacentHTML(insertPosition, addWrapper(featureSVGString));
let childElement = calcChildElement(svg, insertPosition) as SVGSVGElement;
svg.insertAdjacentHTML(
insertPosition,
addWrapper(featureSVGString, info.name),
);
let childElement = getChildElement(svg, insertPosition) as SVGSVGElement;

const position = info.positions[i];

Expand All @@ -507,8 +549,8 @@ const drawFeature = (
let shiftDirection = i == 1 ? 1 : -1;
if (info.shiftWithEyes) {
// @ts-ignore
position[0] += shiftDirection * face.eye.distance;
position[1] += -1 * face.eye.height;
position[0] += shiftDirection * (face.eye.distance || 0);
position[1] += -1 * (face.eye.height || 0);
// position[1] += 1 * 50 * (1 - fatScale(face.height));
}

Expand Down Expand Up @@ -542,6 +584,7 @@ const drawFeature = (
childElement.setAttribute("opacity", String(feature.opacity));
}

// For stroke editability, mostly face lines that are configurable
if (feature.hasOwnProperty("strokeWidthModifier")) {
scaleStrokeWidthAndChildren(
childElement,
Expand All @@ -563,18 +606,18 @@ const drawFeature = (
}
}

let childElement = calcChildElement(svg, insertPosition) as SVGSVGElement;
let childElement = getChildElement(svg, insertPosition) as SVGSVGElement;

if (
info.scaleFatness &&
info.positions.length === 1 &&
info.positions[0] === null
) {
// TODO - scale Height as well, make it move down
// @ts-ignore
scaleCentered(childElement, fatScale(face.fatness), 1);
}

// Mostly just for glasses
if (info.positions.length === 1 && info.shiftWithEyes) {
// @ts-ignore
addTransform(
Expand Down Expand Up @@ -608,7 +651,7 @@ export const display = (
svg.setAttribute("viewBox", "0 0 400 600");
svg.setAttribute("preserveAspectRatio", "xMinYMin meet");

svg.insertAdjacentHTML("beforeend", addWrapper(""));
svg.insertAdjacentHTML("beforeend", addWrapper("", "wrapper"));
let insideSVG = svg.firstChild as SVGSVGElement;

// Needs to be in the DOM here so getBBox will work
Expand Down Expand Up @@ -716,15 +759,31 @@ export const display = (
},
];

paper.setup(document.createElement("canvas"));
let baseFace;

for (const info of featureInfos) {
const feature = face[info.name];
if (!feature.id || feature.id === "none" || feature.id === "") {
continue;
}
drawFeature(insideSVG, face, info);
let svgIndex = svgsIndex[info.name].indexOf(feature.id);
let metadata = svgsMetadata[info.name][svgIndex];

// Set base SVG project after head is added
if (info.name == "head") {
baseFace = paper.project.importSVG(insideSVG);
}

if (metadata.clip) {
clipToParent(insideSVG, baseFace, "beforeend");
}

// After we add hair (which is last feature on face), add outer stroke to wrap entire face
if (info.name == "hair") {
let outerStroke = getOuterStroke(insideSVG);
insideSVG.insertAdjacentHTML(
"beforeend",
outerStroke.exportSVG({ asString: true }),
);
insideSVG.insertAdjacentHTML("beforeend", outerStroke);
}
}

Expand Down
4 changes: 2 additions & 2 deletions svgs/facialHair/goatee-thin-stache.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion svgs/facialHair/goatee-thin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 7 additions & 3 deletions svgs/facialHair/loganSoul.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions svgs/facialHair/sideburns1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions svgs/facialHair/sideburns2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions svgs/facialHair/sideburns3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 1 addition & 5 deletions svgs/hair/cornrows.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions svgs/head/head2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 20 additions & 1 deletion tools/lib/process-svgs.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { optimize } from "svgo";
import { genders } from "./genders.js";
import { svgMetadata } from "./svg-metadata.js";

const warning =
"// THIS IS A GENERATED FILE, DO NOT EDIT BY HAND!\n// See tools/process-svgs.js";
Expand Down Expand Up @@ -57,6 +58,7 @@ const processSVGs = async () => {
for (const key of Object.keys(svgsIndex)) {
svgsIndex[key] = Object.keys(svgsIndex[key]);
}

const svgsGenders = {
...svgsIndex,
};
Expand All @@ -72,11 +74,28 @@ const processSVGs = async () => {
}
svgsGenders[key] = keyGenders;
}

const svgsMetadata = {
...svgsIndex,
};
for (const key of Object.keys(svgsMetadata)) {
const keyMetadata = [];
for (const featureName of svgsMetadata[key]) {
let metadata = svgMetadata[key][featureName];
metadata.name = featureName;
if (metadata === undefined) {
console.log(`Unknown metadata for ${key}/${featureName}`);
metadata = { gender: "both", occurance: 1 };
}
keyMetadata.push(metadata);
}
svgsMetadata[key] = keyMetadata;
}
fs.writeFileSync(
path.join(__dirname, "..", "..", "src", "svgs-index.ts"),
`${warning}\n\nexport const svgsIndex = ${JSON.stringify(
svgsIndex,
)};\n\nexport const svgsGenders = ${JSON.stringify(svgsGenders)};`,
)};\n\nexport const svgsGenders = ${JSON.stringify(svgsGenders)};\n\nexport const svgsMetadata = ${JSON.stringify(svgsMetadata)};`,
);

console.log(
Expand Down
Loading

0 comments on commit 5d8ecdc

Please sign in to comment.