Skip to content

Commit feb658a

Browse files
authored
Add physics properties (#31)
* feat: add phystics properties * chore: update cargo * chore: adjust scaling (pixel per cm) * fix: rename noob variable * fix: backfill defauls for scene
1 parent 2f6ef70 commit feb658a

File tree

10 files changed

+396
-116
lines changed

10 files changed

+396
-116
lines changed

src-tauri/Cargo.lock

Lines changed: 104 additions & 81 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/canvas/objects/CanvasSprite.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import useImage from 'use-image'
22
import { Image as KonvaImage, Transformer } from 'react-konva'
3-
import React from 'react'
3+
import React, { useEffect } from 'react'
44
import { KonvaNodeEvents } from 'react-konva/ReactKonvaCore'
55
import { ImageConfig } from 'konva/lib/shapes/Image'
66
import Konva from 'konva'
@@ -35,6 +35,12 @@ export const CanvasSprite = ({ id, ...props }: Props) => {
3535
const scaleX = Number(scale.x)
3636
const scaleY = Number(scale.y)
3737

38+
// When image loads, get the width and height, needed for weight calculations
39+
useEffect(() => {
40+
if (!width || !height) return
41+
setSpriteMeta((meta) => ({ ...meta, spriteWidth: width, spriteHeight: height }))
42+
}, [width, height])
43+
3844
const onDragStart = (e: Konva.KonvaEventObject<DragEvent>) => {
3945
setSpriteMeta((meta) => ({ ...meta, isDragging: true }))
4046
}

src/components/menu/scene/LoadScene.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ const NewScene = ({}: Props): JSX.Element => {
8383

8484
// Load defaults
8585
if (scene.defaultProperties) {
86-
setDefaultProperties(scene.defaultProperties)
86+
setDefaultProperties(DefaultProperties.fromSceneFile(scene.defaultProperties))
8787
}
8888

8989
// Load assets

src/components/sidebars/default-properties/DefaultPropertiesSection.tsx

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,9 @@ const DefaultPropertiesSection = ({}: Props): JSX.Element => {
1313
const [defaultProps, setDefaultProps] = useRecoilState(defaultPropertiesState)
1414

1515
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
16-
const { id, value, checked } = e.target
17-
if (e.target.type === 'checkbox') {
18-
setDefaultProps((data) => set(cloneDeep(data), id, checked))
19-
} else {
20-
setDefaultProps((data) => set(cloneDeep(data), id, value))
21-
}
16+
const { id, type, value, checked } = e.target
17+
const newValue = type === 'checkbox' ? checked : value
18+
setDefaultProps((data) => set(cloneDeep(data), id, newValue))
2219
}
2320

2421
return (
@@ -63,12 +60,6 @@ const DefaultPropertiesSection = ({}: Props): JSX.Element => {
6360
/>
6461
</FormRow>
6562

66-
<Box pt="sm">
67-
<Heading size="xs" color="yellow" as="h3">
68-
Other settings
69-
</Heading>
70-
</Box>
71-
7263
<Heading size="xs">Rotation</Heading>
7364
<FormRow>
7465
<Field
@@ -101,10 +92,96 @@ const DefaultPropertiesSection = ({}: Props): JSX.Element => {
10192
<label htmlFor="opacity">Opacity</label>
10293
</FormRow>
10394

95+
<Box pt="sm">
96+
<Heading size="xs" color="yellow" as="h3">
97+
Editor settings
98+
</Heading>
99+
</Box>
100+
104101
<FormRow>
105-
<Box mt="xs">
102+
<Box my="xs">
106103
<Checkbox color="green" id="locked" checked={defaultProps.locked} onChange={onChange} />
107-
<label htmlFor="opacity">Locked</label>
104+
<label htmlFor="locked">Locked</label>
105+
</Box>
106+
</FormRow>
107+
108+
<Box pt="sm">
109+
<Heading size="xs" color="yellow" as="h3">
110+
Physics
111+
</Heading>
112+
</Box>
113+
114+
<Heading size="xs">Rigidbody</Heading>
115+
<FormRow>
116+
<Box my="xs">
117+
<Checkbox
118+
color="green"
119+
id="isStatic"
120+
checked={defaultProps.isStatic}
121+
onChange={onChange}
122+
/>
123+
<label htmlFor="isStatic">Static</label>
124+
</Box>
125+
</FormRow>
126+
127+
<Heading size="xs">Weight</Heading>
128+
<FormRow>
129+
<Box my="xs">
130+
<label htmlFor="useSizeForWeight">
131+
<Checkbox
132+
color="white"
133+
id="useSizeForWeight"
134+
checked={defaultProps.useSizeForWeight}
135+
onChange={onChange}
136+
/>{' '}
137+
<Field
138+
style={{ width: '6em' }}
139+
disabled={!defaultProps.useSizeForWeight}
140+
color="green"
141+
type="number"
142+
max="10000"
143+
min="0"
144+
id="sizeToWeightMultiplier"
145+
value={defaultProps.sizeToWeightMultiplier}
146+
onChange={onChange}
147+
/>{' '}
148+
kg per sqm
149+
</label>
150+
</Box>
151+
</FormRow>
152+
153+
<FormRow>
154+
<Box my="xs">
155+
<label htmlFor="notUseSizeForWeight">
156+
<Checkbox
157+
color="white"
158+
id="notUseSizeForWeight"
159+
checked={!defaultProps.useSizeForWeight}
160+
onChange={(e) =>
161+
onChange({
162+
...e,
163+
target: {
164+
...e.target,
165+
id: 'useSizeForWeight',
166+
type: 'checkbox',
167+
checked: !e.target.checked,
168+
},
169+
})
170+
}
171+
/>{' '}
172+
<Field
173+
disabled={defaultProps.useSizeForWeight}
174+
style={{ width: '5em' }}
175+
color="green"
176+
type="number"
177+
max="10000"
178+
min="0"
179+
id="weight"
180+
value={defaultProps.weight}
181+
onChange={onChange}
182+
/>{' '}
183+
kg
184+
</label>
108185
</Box>
109186
</FormRow>
110187
</Section>

src/components/sidebars/inspector/InspectorSection.tsx

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,75 @@
11
import React, { HTMLInputTypeAttribute } from 'react'
22
import { useRecoilState, useRecoilValue } from 'recoil'
3-
import { selectedSpriteIdsState, spriteDatasWithId } from '../../../state/SpritesState'
3+
import {
4+
selectedSpriteIdsState,
5+
spriteDatasWithId,
6+
spriteMetasWithId,
7+
} from '../../../state/SpritesState'
48
import { Box, Checkbox, Heading } from 'dracula-ui'
59
import Section from '../../layout/Section'
610
import FormRow from '../../atoms/FormRow'
711
import CopyButton from '../../atoms/CopyButton'
812
import { cloneDeep, set } from 'lodash'
913
import { Field } from '../../atoms/Field'
1014

15+
const calculateWeight = (
16+
width: number,
17+
height: number,
18+
scale: Scale,
19+
sizeToWeightMultiplier: number | string,
20+
) => {
21+
return (
22+
((width * Number(scale.x) * height * Number(scale.y)) / 10_000) * Number(sizeToWeightMultiplier)
23+
)
24+
}
25+
26+
const getDerivedValues = (
27+
id: string,
28+
value: any,
29+
spriteData: SpriteData,
30+
spriteMeta: SpriteMeta,
31+
) => {
32+
console.log(id, value, spriteMeta.spriteWidth, spriteMeta.spriteHeight)
33+
switch (id) {
34+
case 'sizeToWeightMultiplier': {
35+
const weight = calculateWeight(
36+
spriteMeta.spriteWidth,
37+
spriteMeta.spriteHeight,
38+
spriteData.scale,
39+
value,
40+
)
41+
return { weight }
42+
}
43+
case 'useSizeForWeight': {
44+
if (value === false) return {}
45+
const weight = calculateWeight(
46+
spriteMeta.spriteWidth,
47+
spriteMeta.spriteHeight,
48+
spriteData.scale,
49+
spriteData.sizeToWeightMultiplier,
50+
)
51+
return { weight }
52+
}
53+
default:
54+
return {}
55+
}
56+
}
57+
1158
export const InspectorSection = (): JSX.Element => {
1259
const [selectionId] = useRecoilValue(selectedSpriteIdsState)
1360
const [spriteData, setSpriteData] = useRecoilState(spriteDatasWithId(selectionId))
61+
const [spriteMeta, _] = useRecoilState(spriteMetasWithId(selectionId))
1462

1563
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
16-
const { id, value, checked } = e.target
17-
if (e.target.type === 'checkbox') {
18-
setSpriteData((data) => set(cloneDeep(data), id, checked))
19-
} else {
20-
setSpriteData((data) => set(cloneDeep(data), id, value))
21-
}
64+
const { id, value, checked, type } = e.target
65+
let newValue = type === 'checkbox' ? checked : value
66+
67+
setSpriteData((data) => {
68+
let newData = cloneDeep(data)
69+
set(newData, id, newValue)
70+
Object.assign(newData, getDerivedValues(id, newValue, spriteData, spriteMeta))
71+
return newData
72+
})
2273
}
2374

2475
return (
@@ -156,7 +207,82 @@ export const InspectorSection = (): JSX.Element => {
156207
<FormRow>
157208
<Box mt="xs">
158209
<Checkbox color="green" id="locked" checked={spriteData.locked} onChange={onChange} />
159-
<label htmlFor="opacity">Locked</label>
210+
<label htmlFor="locked">Locked</label>
211+
</Box>
212+
</FormRow>
213+
214+
<Box pt="sm">
215+
<Heading size="xs" color="yellow" as="h3">
216+
Physics
217+
</Heading>
218+
</Box>
219+
220+
<Heading size="xs">Rigidbody</Heading>
221+
<FormRow>
222+
<Box my="xs">
223+
<Checkbox color="green" id="isStatic" checked={spriteData.isStatic} onChange={onChange} />
224+
<label htmlFor="isStatic">Static</label>
225+
</Box>
226+
</FormRow>
227+
228+
<Heading size="xs">Weight</Heading>
229+
<FormRow>
230+
<Box my="xs">
231+
<label htmlFor="useSizeForWeight">
232+
<Checkbox
233+
color="white"
234+
id="useSizeForWeight"
235+
checked={spriteData.useSizeForWeight}
236+
onChange={onChange}
237+
/>{' '}
238+
<Field
239+
style={{ width: '6em' }}
240+
disabled={!spriteData.useSizeForWeight}
241+
color="green"
242+
type="number"
243+
max="10000"
244+
min="0"
245+
id="sizeToWeightMultiplier"
246+
value={spriteData.sizeToWeightMultiplier}
247+
onChange={onChange}
248+
/>{' '}
249+
kg per sqm
250+
</label>
251+
</Box>
252+
</FormRow>
253+
254+
<FormRow>
255+
<Box my="xs">
256+
<label htmlFor="notUseSizeForWeight">
257+
<Checkbox
258+
color="white"
259+
id="notUseSizeForWeight"
260+
checked={!spriteData.useSizeForWeight}
261+
onChange={(e) =>
262+
onChange({
263+
...e,
264+
target: {
265+
...e.target,
266+
id: 'useSizeForWeight',
267+
type: 'checkbox',
268+
checked: !e.target.checked,
269+
},
270+
})
271+
}
272+
/>{' '}
273+
<Field
274+
disabled={spriteData.useSizeForWeight}
275+
style={{ width: '6em' }}
276+
color="green"
277+
type="number"
278+
max="10000"
279+
min="0"
280+
id="weight"
281+
value={spriteData.weight}
282+
onChange={onChange}
283+
/>{' '}
284+
kg
285+
</label>
160286
</Box>
161287
</FormRow>
162288
</Section>

src/model/DefaultProperties.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ export class DefaultProperties implements Partial<SpriteData> {
66
scale: { x: '1.0', y: '1.0', z: '1.0' },
77
opacity: 1,
88
locked: false,
9+
isStatic: false,
10+
useSizeForWeight: true,
11+
sizeToWeightMultiplier: 50,
12+
weight: 100,
13+
}
14+
}
15+
16+
public static fromSceneFile(
17+
defaultProperties: Partial<DefaultPropertiesProps>,
18+
): DefaultPropertiesProps {
19+
return {
20+
...DefaultProperties.default(),
21+
...defaultProperties,
922
}
1023
}
1124
}

src/model/SpriteData.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ export class SpriteData {
1919
// Whether you can drag the sprite
2020
public locked: boolean = false
2121

22+
// Physics properties
23+
// Whether it can move as an object or not.
24+
public isStatic: boolean = false
25+
// Weight
26+
public useSizeForWeight: boolean = true
27+
public sizeToWeightMultiplier: number = 100
28+
public weight: number = 100
29+
30+
static default() {
31+
const { ...defaults } = new SpriteData()
32+
33+
return defaults
34+
}
35+
2236
static createFromDragAndDrop(
2337
x: number,
2438
y: number,
@@ -27,14 +41,18 @@ export class SpriteData {
2741
): SpriteData {
2842
const { z } = defaults.position
2943
return {
30-
id: uuidv4(),
44+
...SpriteData.default(),
3145
...defaults,
46+
id: uuidv4(),
3247
position: { x, y, z },
3348
relativePath,
3449
}
3550
}
3651

37-
static default() {
38-
return {} as SpriteData
52+
static fromSceneFile(sprite: SpriteData) {
53+
return {
54+
...SpriteData.default(),
55+
...sprite,
56+
}
3957
}
4058
}

0 commit comments

Comments
 (0)