2021-02-27 13:35:34 +00:00
|
|
|
import React, { useContext, useRef, useState } from "react";
|
2021-02-15 13:34:22 +00:00
|
|
|
import { SvgVocabulary } from "../svg-vocabulary";
|
2021-02-15 14:56:02 +00:00
|
|
|
import {
|
|
|
|
createSvgSymbolContext,
|
|
|
|
SvgSymbolContent,
|
2021-02-15 22:19:07 +00:00
|
|
|
SvgSymbolContext,
|
2021-02-15 14:56:02 +00:00
|
|
|
SvgSymbolData,
|
|
|
|
} from "../svg-symbol";
|
2021-02-23 02:50:14 +00:00
|
|
|
import {
|
|
|
|
AttachmentPointType,
|
|
|
|
iterAttachmentPoints,
|
|
|
|
PointWithNormal,
|
|
|
|
} from "../specs";
|
2021-02-16 16:52:52 +00:00
|
|
|
import { getAttachmentTransforms } from "../attach";
|
2021-02-26 02:57:10 +00:00
|
|
|
import { scalePointXY, subtractPoints } from "../point";
|
|
|
|
import { BBox, Point } from "../../vendor/bezier-js";
|
2021-02-16 22:42:19 +00:00
|
|
|
import { Random } from "../random";
|
2021-02-17 13:07:04 +00:00
|
|
|
import { SymbolContextWidget } from "../symbol-context-widget";
|
2021-02-23 02:50:14 +00:00
|
|
|
import { range } from "../util";
|
2021-02-26 02:57:10 +00:00
|
|
|
import { getBoundingBoxCenter, uniformlyScaleToFit } from "../bounding-box";
|
2021-02-27 13:35:34 +00:00
|
|
|
import { AutoSizingSvg } from "../auto-sizing-svg";
|
2021-02-27 13:43:31 +00:00
|
|
|
import { exportSvg } from "../export-svg";
|
2021-02-15 14:56:02 +00:00
|
|
|
|
2021-02-26 00:40:18 +00:00
|
|
|
const DEFAULT_BG_COLOR = "#858585";
|
|
|
|
|
2021-02-15 14:56:02 +00:00
|
|
|
const SYMBOL_MAP = new Map(
|
|
|
|
SvgVocabulary.map((symbol) => [symbol.name, symbol])
|
|
|
|
);
|
|
|
|
|
|
|
|
function getSymbol(name: string): SvgSymbolData {
|
|
|
|
const symbol = SYMBOL_MAP.get(name);
|
|
|
|
if (!symbol) {
|
|
|
|
throw new Error(`Unable to find the symbol "${name}"!`);
|
|
|
|
}
|
|
|
|
return symbol;
|
|
|
|
}
|
2021-02-15 13:34:22 +00:00
|
|
|
|
2021-02-15 21:40:47 +00:00
|
|
|
function getAttachmentPoint(
|
|
|
|
s: SvgSymbolData,
|
|
|
|
type: AttachmentPointType,
|
|
|
|
idx: number = 0
|
|
|
|
): PointWithNormal {
|
|
|
|
const { specs } = s;
|
|
|
|
if (!specs) {
|
|
|
|
throw new Error(`Symbol ${s.name} has no specs!`);
|
|
|
|
}
|
|
|
|
const points = specs[type];
|
|
|
|
if (!(points && points.length > idx)) {
|
|
|
|
throw new Error(
|
2021-02-19 03:25:11 +00:00
|
|
|
`Expected symbol ${s.name} to have at least ${
|
2021-02-15 21:40:47 +00:00
|
|
|
idx + 1
|
|
|
|
} ${type} attachment point(s)!`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return points[idx];
|
|
|
|
}
|
|
|
|
|
2021-02-19 03:25:11 +00:00
|
|
|
function safeGetAttachmentPoint(
|
|
|
|
s: SvgSymbolData,
|
|
|
|
type: AttachmentPointType,
|
|
|
|
idx: number = 0
|
|
|
|
): PointWithNormal | null {
|
|
|
|
try {
|
|
|
|
return getAttachmentPoint(s, type, idx);
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-02-15 22:19:07 +00:00
|
|
|
type AttachmentChildren = JSX.Element | JSX.Element[];
|
|
|
|
|
|
|
|
type CreatureContextType = SvgSymbolContext & {
|
|
|
|
attachmentScale: number;
|
|
|
|
parent: SvgSymbolData | null;
|
|
|
|
};
|
|
|
|
|
2021-02-15 22:22:59 +00:00
|
|
|
const DEFAULT_ATTACHMENT_SCALE = 0.5;
|
|
|
|
|
2021-02-15 22:19:07 +00:00
|
|
|
const CreatureContext = React.createContext<CreatureContextType>({
|
|
|
|
...createSvgSymbolContext(),
|
2021-02-15 22:22:59 +00:00
|
|
|
attachmentScale: DEFAULT_ATTACHMENT_SCALE,
|
2021-02-15 22:19:07 +00:00
|
|
|
parent: null,
|
|
|
|
});
|
|
|
|
|
2021-02-16 22:11:41 +00:00
|
|
|
type AttachmentIndices = {
|
|
|
|
left?: boolean;
|
|
|
|
right?: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
type CreatureSymbolProps = AttachmentIndices & {
|
2021-02-15 22:19:07 +00:00
|
|
|
data: SvgSymbolData;
|
2021-02-26 02:57:10 +00:00
|
|
|
nestInside?: boolean;
|
2021-02-15 22:19:07 +00:00
|
|
|
children?: AttachmentChildren;
|
|
|
|
attachTo?: AttachmentPointType;
|
2021-02-23 02:50:14 +00:00
|
|
|
indices?: number[];
|
2021-02-15 22:19:07 +00:00
|
|
|
};
|
|
|
|
|
2021-02-16 22:11:41 +00:00
|
|
|
function getAttachmentIndices(ai: AttachmentIndices): number[] {
|
|
|
|
const result: number[] = [];
|
|
|
|
|
|
|
|
if (ai.left) {
|
|
|
|
result.push(0);
|
|
|
|
}
|
|
|
|
if (ai.right) {
|
|
|
|
result.push(1);
|
|
|
|
}
|
|
|
|
if (result.length === 0) {
|
|
|
|
result.push(0);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-02-26 02:57:10 +00:00
|
|
|
type SplitCreatureSymbolChildren = {
|
|
|
|
attachments: JSX.Element[];
|
|
|
|
nests: JSX.Element[];
|
|
|
|
};
|
2021-02-15 22:19:07 +00:00
|
|
|
|
2021-02-26 02:57:10 +00:00
|
|
|
function splitCreatureSymbolChildren(
|
|
|
|
children?: AttachmentChildren
|
|
|
|
): SplitCreatureSymbolChildren {
|
|
|
|
const result: SplitCreatureSymbolChildren = {
|
|
|
|
attachments: [],
|
|
|
|
nests: [],
|
|
|
|
};
|
|
|
|
if (!children) return result;
|
|
|
|
|
|
|
|
React.Children.forEach(children, (child) => {
|
|
|
|
if (child.props.nestInside) {
|
|
|
|
result.nests.push(child);
|
|
|
|
} else {
|
|
|
|
result.attachments.push(child);
|
|
|
|
}
|
|
|
|
});
|
2021-02-15 22:19:07 +00:00
|
|
|
|
2021-02-26 02:57:10 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
type ChildCreatureSymbolProps = {
|
|
|
|
symbol: JSX.Element;
|
|
|
|
data: SvgSymbolData;
|
|
|
|
parent: SvgSymbolData;
|
|
|
|
indices: number[];
|
|
|
|
};
|
|
|
|
|
|
|
|
const NestedCreatureSymbol: React.FC<ChildCreatureSymbolProps> = ({
|
|
|
|
symbol,
|
|
|
|
data,
|
|
|
|
parent,
|
|
|
|
indices,
|
|
|
|
}) => {
|
|
|
|
const children: JSX.Element[] = [];
|
|
|
|
|
|
|
|
for (let nestIndex of indices) {
|
|
|
|
const parentNest = (parent.specs?.nesting ?? [])[nestIndex];
|
|
|
|
if (!parentNest) {
|
|
|
|
console.error(
|
|
|
|
`Parent symbol ${parent.name} has no nesting index ${nestIndex}!`
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const t = getNestingTransforms(parentNest, data.bbox);
|
|
|
|
children.push(
|
|
|
|
<AttachmentTransform
|
|
|
|
key={nestIndex}
|
|
|
|
transformOrigin={t.transformOrigin}
|
|
|
|
translate={t.translation}
|
|
|
|
scale={t.scaling}
|
|
|
|
rotate={0}
|
|
|
|
>
|
|
|
|
{symbol}
|
|
|
|
</AttachmentTransform>
|
2021-02-15 22:19:07 +00:00
|
|
|
);
|
|
|
|
}
|
2021-02-16 16:52:52 +00:00
|
|
|
|
2021-02-26 02:57:10 +00:00
|
|
|
return <>{children}</>;
|
|
|
|
};
|
|
|
|
|
|
|
|
const AttachedCreatureSymbol: React.FC<
|
|
|
|
ChildCreatureSymbolProps & {
|
|
|
|
attachTo: AttachmentPointType;
|
|
|
|
}
|
|
|
|
> = ({ symbol, data, parent, indices, attachTo }) => {
|
|
|
|
const ctx = useContext(CreatureContext);
|
2021-02-16 22:11:41 +00:00
|
|
|
const children: JSX.Element[] = [];
|
|
|
|
|
2021-02-26 02:57:10 +00:00
|
|
|
for (let attachIndex of indices) {
|
2021-02-19 03:25:11 +00:00
|
|
|
const parentAp = safeGetAttachmentPoint(parent, attachTo, attachIndex);
|
2021-02-22 02:13:14 +00:00
|
|
|
const ourAp = safeGetAttachmentPoint(data, "anchor");
|
2021-02-19 03:25:11 +00:00
|
|
|
|
|
|
|
if (!parentAp || !ourAp) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-02-16 22:11:41 +00:00
|
|
|
|
|
|
|
// If we're attaching something oriented towards the left, horizontally flip
|
|
|
|
// the attachment image.
|
|
|
|
let xFlip = parentAp.normal.x < 0 ? -1 : 1;
|
|
|
|
|
|
|
|
// Er, things look weird if we don't inverse the flip logic for
|
|
|
|
// the downward-facing attachments, like legs...
|
|
|
|
if (parentAp.normal.y > 0) {
|
|
|
|
xFlip *= -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const t = getAttachmentTransforms(parentAp, {
|
|
|
|
point: ourAp.point,
|
|
|
|
normal: scalePointXY(ourAp.normal, xFlip, 1),
|
|
|
|
});
|
|
|
|
|
|
|
|
children.push(
|
|
|
|
<AttachmentTransform
|
|
|
|
key={attachIndex}
|
|
|
|
transformOrigin={ourAp.point}
|
|
|
|
translate={t.translation}
|
|
|
|
scale={{ x: ctx.attachmentScale * xFlip, y: ctx.attachmentScale }}
|
2021-02-22 02:13:14 +00:00
|
|
|
rotate={xFlip * t.rotation}
|
2021-02-16 22:11:41 +00:00
|
|
|
>
|
2021-02-26 02:57:10 +00:00
|
|
|
{symbol}
|
2021-02-16 22:11:41 +00:00
|
|
|
</AttachmentTransform>
|
|
|
|
);
|
2021-02-16 00:28:03 +00:00
|
|
|
}
|
2021-02-15 22:19:07 +00:00
|
|
|
|
2021-02-16 22:11:41 +00:00
|
|
|
return <>{children}</>;
|
2021-02-15 22:19:07 +00:00
|
|
|
};
|
|
|
|
|
2021-02-26 02:57:10 +00:00
|
|
|
const CreatureSymbol: React.FC<CreatureSymbolProps> = (props) => {
|
|
|
|
const ctx = useContext(CreatureContext);
|
|
|
|
const { data, attachTo, nestInside } = props;
|
|
|
|
const childCtx: CreatureContextType = { ...ctx, parent: data };
|
|
|
|
const { nests, attachments } = splitCreatureSymbolChildren(props.children);
|
|
|
|
|
|
|
|
// The attachments should be before our symbol in the DOM so they
|
|
|
|
// appear behind our symbol, while anything nested within our symbol
|
|
|
|
// should be after our symbol so they appear in front of it.
|
|
|
|
const symbol = (
|
|
|
|
<>
|
|
|
|
{attachments.length && (
|
|
|
|
<CreatureContext.Provider value={childCtx}>
|
|
|
|
{attachments}
|
|
|
|
</CreatureContext.Provider>
|
|
|
|
)}
|
|
|
|
<SvgSymbolContent data={data} {...ctx} />
|
|
|
|
{nests.length && (
|
|
|
|
<CreatureContext.Provider value={childCtx}>
|
|
|
|
{nests}
|
|
|
|
</CreatureContext.Provider>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!(attachTo || nestInside)) {
|
|
|
|
return symbol;
|
|
|
|
}
|
|
|
|
|
|
|
|
const parent = ctx.parent;
|
|
|
|
if (!parent) {
|
|
|
|
throw new Error(
|
|
|
|
`Cannot attach/nest ${props.data.name} because it has no parent!`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const childProps: ChildCreatureSymbolProps = {
|
|
|
|
parent,
|
|
|
|
symbol,
|
|
|
|
data,
|
|
|
|
indices: props.indices || getAttachmentIndices(props),
|
|
|
|
};
|
|
|
|
|
|
|
|
if (attachTo) {
|
|
|
|
return <AttachedCreatureSymbol {...childProps} attachTo={attachTo} />;
|
|
|
|
}
|
|
|
|
|
|
|
|
return <NestedCreatureSymbol {...childProps} />;
|
|
|
|
};
|
|
|
|
|
|
|
|
function getNestingTransforms(parent: BBox, child: BBox) {
|
|
|
|
const parentCenter = getBoundingBoxCenter(parent);
|
|
|
|
const childCenter = getBoundingBoxCenter(child);
|
|
|
|
const translation = subtractPoints(parentCenter, childCenter);
|
|
|
|
const uniformScaling = uniformlyScaleToFit(parent, child);
|
|
|
|
const scaling: Point = { x: uniformScaling, y: uniformScaling };
|
|
|
|
|
|
|
|
return { translation, transformOrigin: childCenter, scaling };
|
|
|
|
}
|
|
|
|
|
2021-02-16 21:43:47 +00:00
|
|
|
type AttachmentTransformProps = {
|
|
|
|
transformOrigin: Point;
|
|
|
|
translate: Point;
|
|
|
|
scale: Point;
|
|
|
|
rotate: number;
|
|
|
|
children: JSX.Element;
|
|
|
|
};
|
|
|
|
|
|
|
|
const AttachmentTransform: React.FC<AttachmentTransformProps> = (props) => (
|
|
|
|
<g transform={`translate(${props.translate.x} ${props.translate.y})`}>
|
2021-02-17 17:37:24 +00:00
|
|
|
{/**
|
|
|
|
* We originally used "transform-origin" here but that's not currently
|
|
|
|
* supported by Safari. Instead, we'll set the origin of our symbol to
|
|
|
|
* the transform origin, do the transform, and then move our origin back to
|
|
|
|
* the original origin, which is equivalent to setting "transform-origin".
|
|
|
|
**/}
|
2021-02-16 21:43:47 +00:00
|
|
|
<g
|
2021-02-17 17:37:24 +00:00
|
|
|
transform={`translate(${props.transformOrigin.x} ${props.transformOrigin.y})`}
|
2021-02-16 21:43:47 +00:00
|
|
|
>
|
2021-02-17 17:37:24 +00:00
|
|
|
<g
|
|
|
|
transform={`scale(${props.scale.x} ${props.scale.y}) rotate(${props.rotate})`}
|
|
|
|
>
|
|
|
|
<g
|
|
|
|
transform={`translate(-${props.transformOrigin.x} -${props.transformOrigin.y})`}
|
|
|
|
>
|
|
|
|
{props.children}
|
|
|
|
</g>
|
|
|
|
</g>
|
2021-02-16 21:43:47 +00:00
|
|
|
</g>
|
|
|
|
</g>
|
|
|
|
);
|
|
|
|
|
2021-02-16 22:42:19 +00:00
|
|
|
type CreatureSymbolWithDefaultProps = Omit<CreatureSymbolProps, "data"> & {
|
|
|
|
data?: SvgSymbolData;
|
|
|
|
};
|
|
|
|
|
2021-02-15 22:19:07 +00:00
|
|
|
function createCreatureSymbol(
|
|
|
|
name: string
|
2021-02-16 22:42:19 +00:00
|
|
|
): React.FC<CreatureSymbolWithDefaultProps> {
|
2021-02-15 22:19:07 +00:00
|
|
|
const data = getSymbol(name);
|
2021-02-16 22:11:41 +00:00
|
|
|
return (props) => <CreatureSymbol data={props.data || data} {...props} />;
|
2021-02-15 22:19:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const Eye = createCreatureSymbol("eye");
|
|
|
|
|
|
|
|
const Hand = createCreatureSymbol("hand");
|
|
|
|
|
2021-02-16 00:28:03 +00:00
|
|
|
const Arm = createCreatureSymbol("arm");
|
2021-02-15 22:19:07 +00:00
|
|
|
|
2021-02-16 01:12:14 +00:00
|
|
|
const Antler = createCreatureSymbol("antler");
|
|
|
|
|
|
|
|
const Crown = createCreatureSymbol("crown");
|
|
|
|
|
2021-02-16 01:20:41 +00:00
|
|
|
const Wing = createCreatureSymbol("wing");
|
|
|
|
|
2021-02-19 01:06:41 +00:00
|
|
|
const MuscleArm = createCreatureSymbol("muscle_arm");
|
2021-02-16 01:20:41 +00:00
|
|
|
|
2021-02-16 03:51:35 +00:00
|
|
|
const Leg = createCreatureSymbol("leg");
|
|
|
|
|
2021-02-16 01:20:41 +00:00
|
|
|
const Tail = createCreatureSymbol("tail");
|
|
|
|
|
2021-02-26 02:57:10 +00:00
|
|
|
const Lightning = createCreatureSymbol("lightning");
|
|
|
|
|
2021-02-23 02:50:14 +00:00
|
|
|
function getSymbolWithAttachments(
|
|
|
|
numAttachmentKinds: number,
|
|
|
|
rng: Random
|
|
|
|
): JSX.Element {
|
|
|
|
const children: JSX.Element[] = [];
|
|
|
|
const root = rng.choice(SvgVocabulary);
|
|
|
|
if (root.specs) {
|
|
|
|
const attachmentKinds = rng.uniqueChoices(
|
2021-02-23 16:46:00 +00:00
|
|
|
Array.from(iterAttachmentPoints(root.specs))
|
|
|
|
.filter((point) => point.type !== "anchor")
|
|
|
|
.map((point) => point.type),
|
2021-02-23 02:50:14 +00:00
|
|
|
numAttachmentKinds
|
|
|
|
);
|
|
|
|
for (let kind of attachmentKinds) {
|
|
|
|
const attachment = rng.choice(SvgVocabulary);
|
|
|
|
const indices = range(root.specs[kind]?.length ?? 0);
|
|
|
|
children.push(
|
|
|
|
<CreatureSymbol
|
|
|
|
data={attachment}
|
|
|
|
key={children.length}
|
|
|
|
attachTo={kind}
|
|
|
|
indices={indices}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return <CreatureSymbol data={root} children={children} />;
|
|
|
|
}
|
|
|
|
|
2021-02-16 22:11:41 +00:00
|
|
|
const EYE_CREATURE = (
|
|
|
|
<Eye>
|
2021-02-26 02:57:10 +00:00
|
|
|
<Lightning nestInside />
|
2021-02-16 22:11:41 +00:00
|
|
|
<Arm attachTo="arm" left>
|
|
|
|
<Wing attachTo="arm" left right />
|
|
|
|
</Arm>
|
|
|
|
<Arm attachTo="arm" right>
|
|
|
|
<MuscleArm attachTo="arm" left right />
|
|
|
|
</Arm>
|
|
|
|
<Antler attachTo="horn" left right />
|
|
|
|
<Crown attachTo="crown">
|
|
|
|
<Hand attachTo="horn" left right>
|
|
|
|
<Arm attachTo="arm" left />
|
|
|
|
</Hand>
|
|
|
|
</Crown>
|
|
|
|
<Leg attachTo="leg" left right />
|
|
|
|
<Tail attachTo="tail" />
|
|
|
|
</Eye>
|
|
|
|
);
|
|
|
|
|
2021-02-16 22:42:19 +00:00
|
|
|
function randomlyReplaceParts(rng: Random, creature: JSX.Element): JSX.Element {
|
|
|
|
return React.cloneElement<CreatureSymbolWithDefaultProps>(creature, {
|
|
|
|
data: rng.choice(SvgVocabulary),
|
|
|
|
children: React.Children.map(creature.props.children, (child, i) => {
|
|
|
|
return randomlyReplaceParts(rng, child);
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-02-23 02:50:14 +00:00
|
|
|
type CreatureGenerator = (rng: Random) => JSX.Element;
|
|
|
|
|
|
|
|
const COMPLEXITY_LEVEL_GENERATORS: CreatureGenerator[] = [
|
|
|
|
...range(5).map((i) => getSymbolWithAttachments.bind(null, i)),
|
|
|
|
(rng) => randomlyReplaceParts(rng, EYE_CREATURE),
|
|
|
|
];
|
|
|
|
|
|
|
|
const MAX_COMPLEXITY_LEVEL = COMPLEXITY_LEVEL_GENERATORS.length - 1;
|
|
|
|
|
2021-02-17 14:14:06 +00:00
|
|
|
function getDownloadFilename(randomSeed: number | null) {
|
|
|
|
let downloadBasename = "mystic-symbolic-creature";
|
|
|
|
|
|
|
|
if (randomSeed !== null) {
|
|
|
|
downloadBasename += `-${randomSeed}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
return `${downloadBasename}.svg`;
|
|
|
|
}
|
2021-02-17 01:54:05 +00:00
|
|
|
|
|
|
|
export const CreaturePage: React.FC<{}> = () => {
|
2021-02-17 14:14:06 +00:00
|
|
|
const svgRef = useRef<SVGSVGElement>(null);
|
2021-02-26 00:40:18 +00:00
|
|
|
const [bgColor, setBgColor] = useState(DEFAULT_BG_COLOR);
|
2021-02-16 22:42:19 +00:00
|
|
|
const [randomSeed, setRandomSeed] = useState<number | null>(null);
|
2021-02-17 13:07:04 +00:00
|
|
|
const [symbolCtx, setSymbolCtx] = useState(createSvgSymbolContext());
|
2021-02-23 02:50:14 +00:00
|
|
|
const [complexity, setComplexity] = useState(MAX_COMPLEXITY_LEVEL);
|
2021-02-16 03:36:18 +00:00
|
|
|
const defaultCtx = useContext(CreatureContext);
|
2021-02-23 02:50:14 +00:00
|
|
|
const newRandomSeed = () => setRandomSeed(Date.now());
|
2021-02-16 03:36:18 +00:00
|
|
|
const ctx: CreatureContextType = {
|
|
|
|
...defaultCtx,
|
2021-02-17 13:07:04 +00:00
|
|
|
...symbolCtx,
|
|
|
|
fill: symbolCtx.showSpecs ? "none" : symbolCtx.fill,
|
2021-02-16 03:36:18 +00:00
|
|
|
};
|
2021-02-16 22:42:19 +00:00
|
|
|
const creature =
|
|
|
|
randomSeed === null
|
|
|
|
? EYE_CREATURE
|
2021-02-23 02:50:14 +00:00
|
|
|
: COMPLEXITY_LEVEL_GENERATORS[complexity](new Random(randomSeed));
|
2021-02-17 14:14:06 +00:00
|
|
|
const handleSvgExport = () =>
|
|
|
|
exportSvg(getDownloadFilename(randomSeed), svgRef);
|
2021-02-16 03:36:18 +00:00
|
|
|
|
2021-02-15 13:34:22 +00:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<h1>Creature!</h1>
|
2021-02-20 16:38:04 +00:00
|
|
|
<SymbolContextWidget ctx={symbolCtx} onChange={setSymbolCtx}>
|
|
|
|
<label htmlFor="bgColor">Background: </label>
|
|
|
|
<input
|
|
|
|
type="color"
|
|
|
|
value={bgColor}
|
|
|
|
onChange={(e) => setBgColor(e.target.value)}
|
|
|
|
/>{" "}
|
|
|
|
</SymbolContextWidget>
|
2021-02-17 01:47:12 +00:00
|
|
|
<p>
|
2021-02-23 02:50:14 +00:00
|
|
|
<label htmlFor="complexity">Random creature complexity: </label>
|
|
|
|
<input
|
|
|
|
type="range"
|
|
|
|
min={0}
|
|
|
|
max={MAX_COMPLEXITY_LEVEL}
|
|
|
|
step={1}
|
|
|
|
value={complexity}
|
|
|
|
onChange={(e) => {
|
|
|
|
setComplexity(parseInt(e.target.value));
|
|
|
|
newRandomSeed();
|
|
|
|
}}
|
|
|
|
/>{" "}
|
|
|
|
{complexity === MAX_COMPLEXITY_LEVEL ? "bonkers" : complexity}
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
<button accessKey="r" onClick={newRandomSeed}>
|
2021-02-20 16:07:57 +00:00
|
|
|
<u>R</u>andomize!
|
|
|
|
</button>{" "}
|
2021-02-17 14:14:06 +00:00
|
|
|
<button onClick={() => window.location.reload()}>Reset</button>{" "}
|
|
|
|
<button onClick={handleSvgExport}>Export SVG</button>
|
2021-02-17 01:47:12 +00:00
|
|
|
</p>
|
2021-02-16 03:36:18 +00:00
|
|
|
<CreatureContext.Provider value={ctx}>
|
2021-02-20 16:38:04 +00:00
|
|
|
<AutoSizingSvg padding={20} ref={svgRef} bgColor={bgColor}>
|
2021-02-17 13:46:30 +00:00
|
|
|
<g transform="scale(0.5 0.5)">{creature}</g>
|
2021-02-17 01:54:05 +00:00
|
|
|
</AutoSizingSvg>
|
2021-02-16 03:36:18 +00:00
|
|
|
</CreatureContext.Provider>
|
2021-02-15 13:34:22 +00:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|