tweaks for cloud shape (#1723)

This makes the bumps on the curvy parts more bumpy, and improves the way
inky clouds are drawn to make it less likely to produce double lines
that do not fully overlap.

<img width="1066" alt="image"
src="https://github.com/tldraw/tldraw/assets/1242537/6119c6e8-ceee-4cf6-b393-70efbbdd6373">


### Change Type

- [x] `patch` — Bug fix
- [ ] `minor` — New feature
- [ ] `major` — Breaking change
- [ ] `dependencies` — Changes to package dependencies[^1]
- [ ] `documentation` — Changes to the documentation only[^2]
- [ ] `tests` — Changes to any test code only[^2]
- [ ] `internal` — Any other changes that don't affect the published
package[^2]
- [ ] I don't know

[^1]: publishes a `patch` release, for devDependencies use `internal`
[^2]: will not publish a new version

### Test Plan

1. Add a step-by-step description of how to test your PR here.
2.

- [ ] Unit Tests
- [ ] End to end tests

### Release Notes

- Add a brief release note for your PR here.
This commit is contained in:
David Sheldrick 2023-07-10 08:55:28 +01:00 committed by GitHub
parent 83a391b46b
commit eeb41d4e69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -135,62 +135,84 @@ export function getCloudArcs(
const paddingX = (width - innerWidth) / 2
const paddingY = (height - innerHeight) / 2
const distanceBetweenPointsOnPerimeter = getPillCircumference(innerWidth, innerHeight) / numBumps
const bumpPoints = getPillPoints(innerWidth, innerHeight, numBumps).map((p) => {
return p.addXY(paddingX, paddingY)
})
const maxWiggle = targetBumpProtrusion * 0.3
const adjustedBumpPoints = bumpPoints.map((p) => {
return Vec2d.AddXY(p, getRandom() * maxWiggle, getRandom() * maxWiggle)
})
// wiggle the points from either end so that the bumps 'pop'
// in at the bottom-right and the top-left looks relatively stable
const wiggledPoints = bumpPoints.slice(0)
for (let i = 0; i < Math.floor(numBumps / 2); i++) {
wiggledPoints[i] = Vec2d.AddXY(
wiggledPoints[i],
getRandom() * maxWiggle,
getRandom() * maxWiggle
)
wiggledPoints[numBumps - i - 1] = Vec2d.AddXY(
wiggledPoints[numBumps - i - 1],
getRandom() * maxWiggle,
getRandom() * maxWiggle
)
}
const arcs: Arc[] = []
for (let i = 0; i < adjustedBumpPoints.length; i++) {
const leftPoint = adjustedBumpPoints[i]
const rightPoint = adjustedBumpPoints[i === adjustedBumpPoints.length - 1 ? 0 : i + 1]
for (let i = 0; i < wiggledPoints.length; i++) {
const j = i === wiggledPoints.length - 1 ? 0 : i + 1
const leftWigglePoint = wiggledPoints[i]
const rightWigglePoint = wiggledPoints[j]
const leftPoint = bumpPoints[i]
const rightPoint = bumpPoints[j]
arcs.push(getCloudArc(leftPoint, rightPoint, Math.max(paddingX, paddingY), width, height))
const midPoint = Vec2d.Average([leftPoint, rightPoint])
const offsetAngle = Vec2d.Angle(leftPoint, rightPoint) - Math.PI / 2
// when the points are on the curvy part of a pill, there is a natural arc that we need to extends past
// otherwise it looks like the bumps get less bumpy on the curvy parts
const distanceBetweenOriginalPoints = Vec2d.Dist(leftPoint, rightPoint)
const curvatureOffset = distanceBetweenPointsOnPerimeter - distanceBetweenOriginalPoints
const distanceBetweenWigglePoints = Vec2d.Dist(leftWigglePoint, rightWigglePoint)
const relativeSize = distanceBetweenWigglePoints / distanceBetweenOriginalPoints
const finalDistance = (Math.max(paddingX, paddingY) + curvatureOffset) * relativeSize
const arcPoint = Vec2d.Add(midPoint, Vec2d.FromAngle(offsetAngle, finalDistance))
if (arcPoint.x < 0) {
arcPoint.x = 0
} else if (arcPoint.x > width) {
arcPoint.x = width
}
if (arcPoint.y < 0) {
arcPoint.y = 0
} else if (arcPoint.y > height) {
arcPoint.y = height
}
const center = getCenterOfCircleGivenThreePoints(leftWigglePoint, rightWigglePoint, arcPoint)
const radius = Vec2d.Dist(center, leftWigglePoint)
arcs.push({
leftPoint: leftWigglePoint,
rightPoint: rightWigglePoint,
arcPoint,
center,
radius,
})
}
return arcs
}
export function getCloudArc(
leftPoint: Vec2d,
rightPoint: Vec2d,
padding: number,
width: number,
height: number
) {
const midPoint = Vec2d.Average([leftPoint, rightPoint])
const offsetAngle = Vec2d.Angle(leftPoint, rightPoint) - Math.PI / 2
const arcPoint = Vec2d.Add(midPoint, Vec2d.FromAngle(offsetAngle, padding))
if (arcPoint.x < 0) {
arcPoint.x = 0
} else if (arcPoint.x > width) {
arcPoint.x = width
}
if (arcPoint.y < 0) {
arcPoint.y = 0
} else if (arcPoint.y > height) {
arcPoint.y = height
}
const center = getCenterOfCircleGivenThreePoints(leftPoint, rightPoint, arcPoint)
const radius = Vec2d.Dist(center, leftPoint)
return {
leftPoint,
rightPoint,
center,
radius,
}
type Arc = {
leftPoint: Vec2d
rightPoint: Vec2d
arcPoint: Vec2d
center: Vec2d
radius: number
}
type Arc = ReturnType<typeof getCloudArc>
function getCenterOfCircleGivenThreePoints(a: Vec2d, b: Vec2d, c: Vec2d) {
const A = a.x * (b.y - c.y) - a.y * (b.x - c.x) + b.x * c.y - c.x * b.y
const B =
@ -259,18 +281,21 @@ export function inkyCloudSvgPath(
const multiplier = size === 's' ? 0.5 : size === 'm' ? 0.7 : size === 'l' ? 0.9 : 1.6
return n + getRandom() * multiplier * 2
}
const mutPoint = (p: Vec2d) => new Vec2d(mut(p.x), mut(p.y))
const arcs = getCloudArcs(width, height, seed, size)
let pathA = `M${arcs[0].leftPoint.x},${arcs[0].leftPoint.y}`
let pathB = `M${mut(arcs[0].leftPoint.x)},${mut(arcs[0].leftPoint.y)}`
let leftMutPoint = mutPoint(arcs[0].leftPoint)
let pathB = `M${leftMutPoint.x},${leftMutPoint.y}`
// now draw arcs for each circle, starting where it intersects the previous circle, and ending where it intersects the next circle
for (const { rightPoint, radius, center } of arcs) {
for (const { rightPoint, radius, arcPoint } of arcs) {
pathA += ` A${radius},${radius} 0 0,1 ${rightPoint.x},${rightPoint.y}`
const mutX = mut(rightPoint.x)
const mutY = mut(rightPoint.y)
const mutRadius = Vec2d.Dist(center, { x: mutX, y: mutY })
const rightMutPoint = mutPoint(rightPoint)
const mutArcPoint = mutPoint(arcPoint)
const mutCenter = getCenterOfCircleGivenThreePoints(leftMutPoint, rightMutPoint, mutArcPoint)
const mutRadius = Math.abs(Vec2d.Dist(mutCenter, leftMutPoint))
pathB += ` A${mutRadius},${mutRadius} 0 0,1 ${mutX},${mutY}`
pathB += ` A${mutRadius},${mutRadius} 0 0,1 ${rightMutPoint.x},${rightMutPoint.y}`
leftMutPoint = rightMutPoint
}
return pathA + pathB + ' Z'