Skip to content

Commit

Permalink
Merge pull request #301 from jeffeb3/feature/randomize-values
Browse files Browse the repository at this point in the history
randomize layer and effect values
  • Loading branch information
jeffeb3 authored Oct 15, 2024
2 parents 78a9a40 + 2f7af8d commit 1ed10d8
Show file tree
Hide file tree
Showing 38 changed files with 394 additions and 22 deletions.
71 changes: 71 additions & 0 deletions src/common/Model.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { functionValue } from "@/common/util"

const options = []

export default class Model {
Expand All @@ -10,6 +12,7 @@ export default class Model {
usesFonts: false,
dragging: false,
stretch: false,
randomizable: true,
})
}

Expand Down Expand Up @@ -47,4 +50,72 @@ export default class Model {
getOptions() {
return options
}

randomChanges(layer, exclude = []) {
const changes = { id: layer.id }
const options = this.getOptions()

Object.keys(options).forEach((key) => {
if (!exclude.includes(key)) {
const change = this.randomChange(key, layer, options)

if (change != null) {
changes[key] = change
}
}
})

return changes
}

// given an option key, make a random change based on model options
randomChange(key, layer, options) {
const settings = options[key]
const random = settings.random == null ? 1 : settings.random
const randomize = Math.random() <= random
const type = settings.type

if (type == "checkbox") {
const defaults = this.getInitialState()
let value = defaults[key]

if (random && Math.random() <= 0.5) {
value = !value
}

return value
} else if (type == "togglebutton" || type == "dropdown") {
let choices = functionValue(settings.choices, layer)
const choice = Math.floor(Math.random() * choices.length)

return choices[choice]
} else if (type == "number" || type == null) {
const defaults = this.getInitialState()
let min = settings.randomMin
if (min == null) {
min = functionValue(settings.min, layer) || 0
}
let max = settings.randomMax || functionValue(settings.max, layer)
if (max == null) {
max = defaults[key] * 3
}
const step = settings.step || 1

if (randomize) {
const random = Math.random() * (max - min) + min
let precision, value

if (step >= 1) {
value = Math.round(random)
} else {
precision = Math.round(1 / step)
value = Math.round(random * precision) / precision
}

return value
} else {
return min
}
}
}
}
2 changes: 1 addition & 1 deletion src/common/lindenmayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const onSubtypeChange = (subtype, changes, attrs) => {
if (subtype) {
let max = subtype.maxIterations
let min = subtype.minIterations
let iterations = attrs.iterations || 1
let iterations = changes.iterations || attrs.iterations || 1

if (max) {
iterations = Math.min(iterations, max)
Expand Down
5 changes: 5 additions & 0 deletions src/common/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,8 @@ export const scaleByWheel = (size, deltaX, deltaY) => {
return newSize
}
}

// convenience method to handle invocation of a function when present
export const functionValue = (val, arg1, arg2) => {
return typeof val === "function" ? val(arg1, arg2) : val
}
26 changes: 24 additions & 2 deletions src/features/effects/EffectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ import Button from "react-bootstrap/Button"
import Accordion from "react-bootstrap/Accordion"
import { Tooltip } from "react-tooltip"
import { useSelector, useDispatch } from "react-redux"
import { FaTrash, FaCopy, FaPlusSquare } from "react-icons/fa"
import { FaTrash, FaCopy, FaPlusSquare, FaDiceFive } from "react-icons/fa"
import { MdOutlineSettingsBackupRestore } from "react-icons/md"
import {
selectSelectedLayer,
deleteEffect,
selectLayerEffects,
} from "@/features/layers/layersSlice"
import { selectSelectedEffect, restoreDefaults } from "./effectsSlice"
import {
selectSelectedEffect,
restoreDefaults,
randomizeValues,
} from "./effectsSlice"
import EffectEditor from "./EffectEditor"
import EffectList from "./EffectList"
import EffectLayer from "./EffectLayer"
import NewEffect from "./NewEffect"
import CopyEffect from "./CopyEffect"

Expand All @@ -23,6 +28,7 @@ const EffectManager = () => {
const effects = useSelector((state) =>
selectLayerEffects(state, selectedLayer.id),
)
const model = selectedEffect && new EffectLayer(selectedEffect.type).model
const numEffects = effects.length
const [showNewEffect, setShowNewEffect] = useState(false)
const [showCopyEffect, setShowCopyEffect] = useState(false)
Expand All @@ -43,6 +49,10 @@ const EffectManager = () => {
dispatch(restoreDefaults(selectedEffect.id))
}

const handleRandomizeValues = () => {
dispatch(randomizeValues(selectedEffect.id))
}

return (
<div>
<NewEffect
Expand Down Expand Up @@ -118,6 +128,18 @@ const EffectManager = () => {
>
<MdOutlineSettingsBackupRestore />
</Button>
<Tooltip id="tooltip-randomize-effect" />
{model.randomizable && (
<Button
className="layer-button"
variant="light"
data-tooltip-content="Randomize effect values"
data-tooltip-id="tooltip-randomize-effect"
onClick={handleRandomizeValues}
>
<FaDiceFive />
</Button>
)}
</div>
</div>
<EffectEditor />
Expand Down
1 change: 1 addition & 0 deletions src/features/effects/FineTuning.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default class FineTuning extends Effect {
super("fineTuning")
this.label = "Fine tuning"
this.selectGroup = "effects"
this.randomizable = false
}

canChangeSize(state) {
Expand Down
6 changes: 6 additions & 0 deletions src/features/effects/Loop.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const options = {
numLoops: {
title: "Number of loops",
min: 1,
randomMin: 3,
randomMax: 8,
},
transformMethod: {
title: "When transforming shape",
Expand Down Expand Up @@ -43,6 +45,7 @@ const options = {
isVisible: (layer, state) => {
return false
},
type: "hidden",
},
spinEnabled: {
title: "Spin",
Expand All @@ -54,6 +57,7 @@ const options = {
isVisible: (layer, state) => {
return state.spinEnabled
},
randomMax: 10,
},
spinMethod: {
title: "Spin by",
Expand All @@ -75,12 +79,14 @@ const options = {
isVisible: (layer, state) => {
return false
},
type: "hidden",
},
spinSwitchbacks: {
title: "Switchbacks",
isVisible: (layer, state) => {
return state.spinEnabled && state.spinMethod === "constant"
},
randomMax: 4,
},
}

Expand Down
1 change: 1 addition & 0 deletions src/features/effects/Mask.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default class Mask extends Effect {
constructor() {
super("mask")
this.label = "Mask"
this.randomizable = false
}

canMove(state) {
Expand Down
18 changes: 18 additions & 0 deletions src/features/effects/NewEffect.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Col from "react-bootstrap/Col"
import Row from "react-bootstrap/Row"
import Form from "react-bootstrap/Form"
import Button from "react-bootstrap/Button"
import S from "react-switch"
const Switch = S.default ? S.default : S // Fix: https://github.com/vitejs/vite/issues/2139
import Modal from "react-bootstrap/Modal"
import {
selectSelectedLayer,
Expand Down Expand Up @@ -39,6 +41,7 @@ const NewEffect = ({ toggleModal, showModal }) => {
const selectOptions = getEffectSelectOptions()
const [type, setType] = useState(defaultEffect.type)
const [name, setName] = useState(defaultEffect.label)
const [randomize, setRandomize] = useState(false)
const selectedEffect = getEffect(type)
const selectedOption = {
value: selectedEffect.id,
Expand All @@ -64,6 +67,9 @@ const NewEffect = ({ toggleModal, showModal }) => {
setName(event.target.value)
}

const handleRandomizeChange = (value) => {
setRandomize(value)
}
const onEffectAdded = (event) => {
const layer = new EffectLayer(type)
event.preventDefault()
Expand All @@ -75,6 +81,7 @@ const NewEffect = ({ toggleModal, showModal }) => {
name,
},
afterId: selectedEffectId,
randomize,
}),
)
toggleModal()
Expand Down Expand Up @@ -115,6 +122,17 @@ const NewEffect = ({ toggleModal, showModal }) => {
/>
</Col>
</Row>
{selectedEffect.randomizable && (
<Row className="align-items-center mt-2">
<Col sm={5}>Randomize values</Col>
<Col sm={7}>
<Switch
checked={randomize}
onChange={handleRandomizeChange}
/>
</Col>
</Row>
)}
</Modal.Body>

<Modal.Footer>
Expand Down
3 changes: 2 additions & 1 deletion src/features/effects/Track.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const options = {
trackRotation: {
title: "Track rotation",
step: 35,
randomMax: 360,
},
trackShape: {
title: "Track type",
Expand Down Expand Up @@ -79,7 +80,7 @@ export default class Track extends Effect {
width: 50,
height: 50,
maintainAspectRatio: true,
trackShape: "circle",
trackShape: "circular",
trackRotation: 360,
trackSpiralRadiusPct: 0.5,
trackPreserveShape: false,
Expand Down
1 change: 1 addition & 0 deletions src/features/effects/Transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default class Transformer extends Effect {
super("transformer")
this.dragPreview = true
this.label = "Move and resize"
this.randomizable = false
}

canMove(state) {
Expand Down
47 changes: 37 additions & 10 deletions src/features/effects/Warp.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Victor from "victor"
import Effect from "./Effect"
import { effectOptions } from "./EffectLayer"
import { subsample, circle } from "@/common/geometry"
import { evaluate } from "mathjs"

Expand All @@ -9,16 +10,7 @@ const options = {
type: "dropdown",
choices: ["angle", "quad", "circle", "grid", "shear", "custom"],
onChange: (model, changes, state) => {
if (["angle", "quad"].includes(changes.warpType)) {
changes.rotation = 45
} else if (changes.warpType == "shear") {
changes.rotation = 0
changes.x = 0
changes.y = 0
} else if (changes.warpType == "custom") {
changes.height = 40
changes.width = 40
}
onWarpTypeChanged(changes)

return changes
},
Expand Down Expand Up @@ -52,6 +44,19 @@ const options = {
},
}

const onWarpTypeChanged = (changes) => {
if (["angle", "quad"].includes(changes.warpType)) {
changes.rotation = 45
} else if (changes.warpType == "shear") {
changes.rotation = 0
changes.x = 0
changes.y = 0
} else if (changes.warpType == "custom") {
changes.height = 40
changes.width = 40
}
}

export default class Warp extends Effect {
constructor() {
super("warp")
Expand Down Expand Up @@ -223,4 +228,26 @@ export default class Warp extends Effect {
getOptions() {
return options
}

randomChanges(layer) {
const changes = super.randomChanges(layer)
const options = { ...effectOptions }

options.width.randomMax = 100
options.height.randomMax = 100
options.x.randomMin = -50
options.x.randomMax = 50
options.x.random = 0.5
options.y.randomMin = -50
options.y.randomMax = 50
options.y.random = 0.5
changes.width = this.randomChange("width", layer, options)
changes.height = this.randomChange("height", layer, options)
changes.x = this.randomChange("x", layer, options)
changes.x = this.randomChange("y", layer, options)

onWarpTypeChanged(changes)

return changes
}
}
Loading

0 comments on commit 1ed10d8

Please sign in to comment.