Skip to content

Commit 1f5835e

Browse files
committed
Added keyframe deduplication and related options
1 parent b3c520d commit 1f5835e

File tree

4 files changed

+160
-15
lines changed

4 files changed

+160
-15
lines changed

debug_resourcepack/armor_stand.ajmodel

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"meta": {
33
"format_version": "0.0",
4-
"creation_time": 1643556769,
4+
"creation_time": 1643566436,
55
"model_format": "animated_java/ajmodel",
66
"box_uv": false,
77
"settings": {
@@ -37,6 +37,8 @@
3737
"autoDistance": true,
3838
"autoDistanceMovementThreshold": 1,
3939
"manualDistance": 10,
40+
"deduplicatePositionFrames": false,
41+
"deduplicateRotationFrames": true,
4042
"modelTag": "aj.%projectName",
4143
"rootTag": "aj.%projectName.root",
4244
"allBonesTag": "aj.%projectName.bone",

src/exporters/animationExporter.ts

Lines changed: 134 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as fs from 'fs'
44
import { tl } from '../util/intl'
55
import { Path } from '../util/path'
66
import { store } from '../util/store'
7-
import { roundToN } from '../util/misc'
7+
import { isEqualVector, roundToN } from '../util/misc'
88
import { compileMC } from '../compileLangMC'
99
import { removeKeyGently } from '../util/misc'
1010
import { generateTree, TreeBranch, TreeLeaf } from '../util/treeGen'
@@ -41,6 +41,8 @@ interface vanillaAnimationExporterSettings {
4141
autoDistance: number
4242
autoDistanceMovementThreshold: number
4343
manualDistance: number
44+
deduplicatePositionFrames: boolean
45+
deduplicateRotationFrames: boolean
4446
}
4547

4648
interface MCBConfig {
@@ -908,11 +910,19 @@ async function createMCFile(
908910
const boneTrees = Object.fromEntries(
909911
Object.keys(bones).map((v) => [
910912
v,
911-
{ root: '', display: '' },
913+
{
914+
root: { v: '', trimmed: false },
915+
display: { v: '', trimmed: false },
916+
},
912917
])
913918
)
914919

915920
for (const boneName of Object.keys(bones)) {
921+
interface TreeReturn {
922+
v: string
923+
trimmed: boolean
924+
}
925+
916926
function createRootTree(item: TreeBranch | TreeLeaf) {
917927
switch (item.type) {
918928
case 'branch':
@@ -927,6 +937,43 @@ async function createMCFile(
927937
}
928938
}
929939

940+
let lastPos = { x: NaN, y: NaN, z: NaN }
941+
function createDeduplicatedRootTree(
942+
item: TreeBranch | TreeLeaf
943+
): TreeReturn {
944+
switch (item.type) {
945+
case 'branch':
946+
const inside: TreeReturn[] = item.items
947+
.map((v: any) =>
948+
createDeduplicatedRootTree(v)
949+
)
950+
.filter((v) => !v.trimmed)
951+
if (inside.length == 0) {
952+
return { v: '', trimmed: true }
953+
} else if (inside.length == 1) {
954+
return inside[0]
955+
}
956+
// prettier-ignore
957+
return {
958+
v: `execute if score .this ${scoreboards.frame} matches ${item.min}..${item.max - 1} run {
959+
name tree/${boneName}_root_${item.min}-${item.max - 1}
960+
${inside.reduce((p, c) => p + (c.v ? c.v+'\n' : ''), '')}
961+
}`,
962+
trimmed: false
963+
}
964+
case 'leaf':
965+
const pos = getRot(boneName, item)
966+
if (isEqualVector(pos, lastPos)) {
967+
return { v: '', trimmed: true }
968+
}
969+
lastPos = pos
970+
return {
971+
v: `execute if score .this ${scoreboards.frame} matches ${item.index} run tp @s ^${pos.x} ^${pos.y} ^${pos.z} ~ ~`,
972+
trimmed: false,
973+
}
974+
}
975+
}
976+
930977
function createDisplayTree(item: TreeBranch | TreeLeaf) {
931978
switch (item.type) {
932979
case 'branch':
@@ -941,12 +988,53 @@ async function createMCFile(
941988
}
942989
}
943990

944-
boneTrees[boneName].root = animationTree.items
945-
.map((v: any) => createRootTree(v))
946-
.join('\n')
947-
boneTrees[boneName].display = animationTree.items
948-
.map((v: any) => createDisplayTree(v))
949-
.join('\n')
991+
let lastRot = { x: NaN, y: NaN, z: NaN }
992+
function createDeduplicatedDisplayTree(
993+
item: TreeBranch | TreeLeaf
994+
): TreeReturn {
995+
switch (item.type) {
996+
case 'branch':
997+
const inside: TreeReturn[] = item.items
998+
.map((v: any) =>
999+
createDeduplicatedDisplayTree(v)
1000+
)
1001+
.filter((v) => !v.trimmed)
1002+
if (inside.length == 0) {
1003+
return { v: '', trimmed: true }
1004+
} else if (inside.length == 1) {
1005+
return inside[0]
1006+
}
1007+
// prettier-ignore
1008+
return {
1009+
v: `execute if score .this ${scoreboards.frame} matches ${item.min}..${item.max - 1} run {
1010+
name tree/${boneName}_display_${item.min}-${item.max - 1}
1011+
${inside.reduce((p, c) => p + (c.v ? c.v+'\n' : ''), '')}
1012+
}`,
1013+
trimmed: false
1014+
}
1015+
case 'leaf':
1016+
const rot = getRot(boneName, item)
1017+
if (isEqualVector(rot, lastRot)) {
1018+
return { v: '', trimmed: true }
1019+
}
1020+
lastRot = rot
1021+
return {
1022+
v: `execute if score .this ${scoreboards.frame} matches ${item.index} run data modify entity @s Pose.Head set value [${rot.x}f,${rot.y}f,${rot.z}f]`,
1023+
trimmed: false,
1024+
}
1025+
}
1026+
}
1027+
1028+
// prettier-ignore
1029+
boneTrees[boneName].root =
1030+
exporterSettings.deduplicatePositionFrames
1031+
? createDeduplicatedRootTree(animationTree)
1032+
: { v: createRootTree(animationTree), trimmed: false }
1033+
// prettier-ignore
1034+
boneTrees[boneName].display =
1035+
exporterSettings.deduplicatePositionFrames
1036+
? createDeduplicatedDisplayTree(animationTree)
1037+
: { v: createDisplayTree(animationTree), trimmed: false }
9501038
}
9511039
return boneTrees
9521040
}
@@ -1039,22 +1127,28 @@ async function createMCFile(
10391127
# Bone Roots
10401128
execute if entity @s[type=${entityTypes.boneRoot}] run {
10411129
name tree/root_bone_name
1042-
${Object.entries(boneTrees).map(([boneName,trees]) =>
1043-
`execute if entity @s[tag=${format(tags.individualBone, {boneName})}] run {
1130+
${Object.entries(boneTrees).map(([boneName,trees]) => {
1131+
// Remove trimmed bone trees (Though there will never be any)
1132+
if (trees.root.trimmed) return ''
1133+
return `execute if entity @s[tag=${format(tags.individualBone, {boneName})}] run {
10441134
name tree/${boneName}_root_top
1045-
${trees.root}
1135+
${trees.root.v}
10461136
}`
1137+
}
10471138
).join('\n')}
10481139
execute store result entity @s Air short 1 run scoreboard players get .this ${scoreboards.frame}
10491140
}
10501141
# Bone Displays
10511142
execute if entity @s[type=${entityTypes.boneDisplay}] run {
10521143
name tree/display_bone_name
1053-
${Object.entries(boneTrees).map(([boneName,trees]) =>
1054-
`execute if entity @s[tag=${format(tags.individualBone, {boneName})}] run {
1144+
${Object.entries(boneTrees).map(([boneName,trees]) => {
1145+
// Remove trimmed bone trees (Though there will never be any)
1146+
if (trees.display.trimmed) return ''
1147+
return `execute if entity @s[tag=${format(tags.individualBone, {boneName})}] run {
10551148
name tree/${boneName}_display_top
1056-
${trees.display}
1149+
${trees.display.v}
10571150
}`
1151+
}
10581152
).join('\n')}
10591153
# Make sure rotation stays aligned with root entity
10601154
execute positioned as @s run tp @s ~ ~ ~ ~ ~
@@ -1487,6 +1581,32 @@ const Exporter = (AJ: any) => {
14871581
},
14881582
dependencies: ['vanillaAnimationExporter.autoDistance'],
14891583
},
1584+
deduplicatePositionFrames: {
1585+
title: tl(
1586+
'animatedJava.exporters.vanillaAnimation.settings.deduplicatePositionFrames.title'
1587+
),
1588+
description: tl(
1589+
'animatedJava.exporters.vanillaAnimation.settings.deduplicatePositionFrames.description'
1590+
),
1591+
type: 'checkbox',
1592+
default: false,
1593+
onUpdate(d: aj.SettingDescriptor) {
1594+
return d
1595+
},
1596+
},
1597+
deduplicateRotationFrames: {
1598+
title: tl(
1599+
'animatedJava.exporters.vanillaAnimation.settings.deduplicateRotationFrames.title'
1600+
),
1601+
description: tl(
1602+
'animatedJava.exporters.vanillaAnimation.settings.deduplicateRotationFrames.description'
1603+
),
1604+
type: 'checkbox',
1605+
default: true,
1606+
onUpdate(d: aj.SettingDescriptor) {
1607+
return d
1608+
},
1609+
},
14901610
modelTag: {
14911611
title: tl(
14921612
'animatedJava.exporters.generic.settings.modelTag.title'

src/lang/en.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,19 @@ animatedJava:
413413
errors:
414414
valueOutOfRange: Setting value must be at least 0
415415

416+
deduplicatePositionFrames:
417+
title: Deduplicate Position Frames
418+
description:
419+
- Removes repeating position values from the exported animation.
420+
- Can greatly reduce file size and function count.
421+
- WARNING! This will make your model drop bones if you attempt to move it. Only enable this setting if you want a stationary model.
422+
423+
deduplicateRotationFrames:
424+
title: Deduplicate Rotation Frames
425+
description:
426+
- Removes repeating rotation values from the exported animation.
427+
- Can greatly reduce file size and function count.
428+
416429
frameScoreboardObjective:
417430
title: Frame Scoreboard
418431
description:

src/util/misc.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,13 @@ export function promiseWhen(condition: any, timeout: number) {
6666
})()
6767
)
6868
}
69+
70+
interface SimpleVector3 {
71+
x: number
72+
y: number
73+
z: number
74+
}
75+
76+
export function isEqualVector(a: SimpleVector3, b:SimpleVector3) {
77+
return a.x == b.x && a.y == b.y && a.z == b.z
78+
}

0 commit comments

Comments
 (0)