Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 75 additions & 13 deletions emotecraft/creatingemotes/custom_pivot_points.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ description: "How to add custom pivot points and bones to emotes."
---

# Custom Pivot Points/Bones
You can add custom bones to the Blockbench model AS LONG AS THEY ARE A CHILD OF THE BODY BONE.
For example you could add a hip bone that rotates all the bones except the legs around well... the hips.
You can add custom bones to the Blockbench model AS LONG AS THEY ARE A CHILD OF THE BODY BONE.
For example you could add a hip bone that rotates all the bones except the legs around well... the hips.
But for this to work you have to register the new bone you have created by adding something like this to the animation JSON:
```json5
{
"model": {
"bone_name": {
"pivot": [0, 12, 0]
Expand All @@ -18,19 +19,80 @@ But for this to work you have to register the new bone you have created by addin
"left_arm": "bone_name",
"torso": "bone_name",
"head": "bone_name"
},
}
}
```
You can either add this to the root of the JSON or under a specific animation.
Replace `bone_name` with the name of your custom bone.
Replace the bones in parents which whatever bones are the children of your custom bone.
And last but not least replace the pivot value with the pivot point of your custom bone.
You can add as many custom bones as you want, and even have custom bones with other custom bones as their children.
You could also add a bone with no children as some sort of marker.
You can either add this to the root of the JSON or under a specific animation.
Replace `bone_name` with the name of your custom bone.
Replace the bones in parents with whatever bones are the children of your custom bone.
And last but not least replace the pivot value with the pivot point of your custom bone.
You can add as many custom bones as you want, and even have custom bones with other custom bones as their children.
You could also add a bone with no children as some sort of marker.

:::warning

If you have read the fading in/out page of the wiki you know that `endTick` uses the easing of the last keyframe.
Well, this does not apply to custom bones.
So if the bones affected by a custom bone have a keyframe their easings will be used, if not then it will default to in out sine.
If you have read the fading in/out page of the wiki you know that `endTick` uses the easing of the last keyframe.
Well, this does not apply to custom bones.
So if the bones affected by a custom bone have a keyframe their easings will be used, if not then it will default to in out sine.

:::

## Custom Model Bones

Custom bones can also have their own 3D model attached. This allows you to render custom geometry that moves with the bone during animations.

To add a model to a custom bone, include `texture` and `elements` fields alongside `pivot` in the `model` section:

```json5
{
"model": {
"bone_name": {
"pivot": [0, 12, 0],
"texture": "<base64 encoded PNG>",
"elements": [
{
"from": [0, 0.25, 0],
"to": [16, 0.25, 16],
"faces": {
"down": { "uv": [0, 16, 16, 0], "texture": "#texture", "tintindex": 0 },
"up": { "uv": [0, 0, 16, 16], "texture": "#texture", "tintindex": 0 }
}
}
]
}
},
"animations": {
"my_animation": {
"loop": true,
"bones": {
"bone_name": {
"rotation": {
"vector": [0, "q.anim_time * 20", 0]
}
}
}
}
}
}
```

- `texture` — a base64-encoded PNG image used as the texture for the model.
- `elements` — an array of block model elements, using the same format as [Minecraft block models](https://minecraft.wiki/w/Tutorials/Models#Block_models). Each element can have `from`, `to`, `faces`, and `rotation` fields.

:::info

`texture` and `elements` are optional. If they are not provided, the bone will act as a regular pivot bone without any visible model.

:::

### Custom Bone Models Editor

Instead of manually writing the `texture` and `elements` fields, you can use the [Custom Bone Models Editor](/cbm-editor) to attach existing Minecraft models (.json or .bbmodel) to bones in your animation.

:::
The editor allows you to:
1. Load your emote JSON
2. Load a Minecraft model (.json or .bbmodel)
3. Map textures (automatic for .bbmodel files with embedded textures)
4. Select a target bone
5. Merge the model into the bone and build a texture atlas automatically
6. Save the resulting JSON
29 changes: 29 additions & 0 deletions src/components/FileDrop.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.drop {
border: 2px dashed var(--ifm-color-emphasis-300);
border-radius: var(--ifm-global-radius);
padding: 2.5rem 1rem;
text-align: center;
cursor: pointer;
transition: border-color 0.15s ease, background-color 0.15s ease;
}

.drop:hover,
.dropOver {
border-color: var(--ifm-color-primary);
background-color: var(--ifm-color-emphasis-100);
}

.dropTitle {
display: block;
font-weight: 600;
margin-bottom: 0.25rem;
}

.dropHint {
color: var(--ifm-color-emphasis-700);
font-size: 0.875rem;
}

.hidden {
display: none;
}
47 changes: 47 additions & 0 deletions src/components/FileDrop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, {useRef, useState} from 'react';
import styles from './FileDrop.module.css';

type Props = {
title: string;
hint: string;
accept: string;
onFile: (file: File) => void;
};

/** A dashed area that takes a file either dropped onto it or picked after a click. */
export default function FileDrop({title, hint, accept, onFile}: Props): React.ReactElement {
const [over, setOver] = useState(false);
const input = useRef<HTMLInputElement>(null);

return (
<div
className={over ? `${styles.drop} ${styles.dropOver}` : styles.drop}
onClick={() => input.current?.click()}
onDragOver={(event) => {
event.preventDefault();
setOver(true);
}}
onDragLeave={() => setOver(false)}
onDrop={(event) => {
event.preventDefault();
setOver(false);
const file = event.dataTransfer.files[0];
if (file) onFile(file);
}}>
<span className={styles.dropTitle}>{title}</span>
<span className={styles.dropHint}>{hint}</span>
<input
ref={input}
className={styles.hidden}
type="file"
accept={accept}
onChange={(event) => {
const file = event.target.files?.[0];
// Otherwise picking the same file again, say after saving it once more, does nothing
event.target.value = '';
if (file) onFile(file);
}}
/>
</div>
);
}
40 changes: 40 additions & 0 deletions src/pages/cbm-editor.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.textures {
list-style: none;
margin: 1rem 0 0;
padding: 0;
}

.texture {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.35rem 0;
}

.textureName {
flex: 1;
min-width: 0;
}

/* Squares behind the transparent parts of a texture, in whichever colours the theme has */
.thumb,
.atlas {
image-rendering: pixelated;
background: repeating-conic-gradient(var(--ifm-color-emphasis-200) 0% 25%, transparent 0% 50%) 0 0 / 8px 8px;
}

.thumb {
flex: none;
width: 2rem;
height: 2rem;
object-fit: contain;
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: var(--ifm-global-radius);
}

.atlas {
display: block;
max-width: 100%;
height: auto;
margin-bottom: 1rem;
}
Loading