Three.js Bone-Local Weapon Attachment: World vs Local Transform

By GripForge editorial team · · 5 min read

How to attach a weapon to a Three.js character bone correctly, and how to diagnose double-applied transforms during idle, turn and attack.

A straightforward way to attach a weapon in Three.js is to parent the prop to the intended bone and apply a bone-local transform. This lets the scene hierarchy carry it with the animated character. A separate world-space attachment system can also work, but it must handle coordinate conversion and update order correctly. Problems arise when world-space values are applied as local values or when two systems both update the attachment.

GripForge's gripforge_attach tool returns a bind transform (position, rotation, rotationOrder, and a baseQuat correction) plus a ready-to-use Three.js snippet. Treat that exported snippet as the source of truth. Don't hand-roll quaternion multiplication order from scratch — small order mistakes are exactly what cause the "works at idle, breaks on turn" bug described below. See the attachment documentation for the exact fields the bind output contains.

The hierarchy you're actually placing the weapon in

Three.js bones participate in the normal scene graph, so a weapon's final position is the product of every ancestor transform above it:

Scene
└─ CharacterRoot (root scale / world position)
   └─ Rig hierarchy
      └─ Hand bone (e.g. RightHand, or a rig-specific alias)
         └─ Weapon (bone-local position/rotation from bind output)

The diagram is conceptual: the bone objects belong to the scene hierarchy, while a THREE.Skeleton is the structure that tracks bones and their skinning data, not an extra scene node to parent under.

With ordinary scene-graph parenting, the weapon inherits its bone's transform when matrices are updated. Keep animation and matrix updates consistent with your application's render loop. You do not also need to copy the bone's world transform into the child's local transform each frame.

Diagnosing double-applied transforms

"Double-applied" means some correction — a rotation, an offset, a scale — is being added twice: once inside the bind data GripForge already computed, and again in your own code. The result is consistent but wrong, or wrong only under motion. Two common patterns:

Pattern 1 — a correction applied twice. A bind can contain position, rotation, a rotation order and baseQuat. Start with the generated snippet, which shows how those fields are used together. An extra correction copied from a different integration example may apply an adjustment twice. Establish a working baseline before introducing any deliberately authored per-character grip offset.

Pattern 2 — world values written into local properties. If a weapon is already a bone child, its position and orientation are relative to that parent. Writing the bone's world transform into those local properties mixes coordinate spaces. In a separate manual attachment system, reading the bone before animation or matrix updates can also introduce lag. Identify which system owns the final transform before changing its update order.

Validation: idle, turn, attack

Check these in order; each isolates a different failure mode.

  1. Idle. Attach the weapon at rest pose. Acceptance: no visible gap or clipping between grip and hand, matching the reference pose the bind was made for.
  2. Turn. Rotate the character root (yaw) through a full turn. Acceptance: the weapon's offset relative to the hand stays constant at every angle — no drift, no independent rotation. If it drifts, inspect the actual parent, inherited transforms and any other code writing the prop transform.
  3. Attack. Play the attack animation and watch the weapon through the fastest arm motion in the clip. Acceptance: the weapon tracks the hand rigidly with no lag, no flip, and no sudden re-orientation partway through. For lag, inspect animation and transform update order. For a flip, inspect rotation representation, the supplied rotation order and other code that changes orientation; the symptom alone does not identify one certain cause.

Symptom / cause checklist

SymptomLikely causeWhat to check
Weapon floats away as character turnsParented to scene/root instead of the boneConfirm the weapon's parent is the bone object itself
Offset is wrong by the same amount at every poseA correction was applied twice (own rotation stacked on baseQuat)Start from the exported snippet; inspect additional rotations for duplicated corrections
Correct at idle, drifts during animationMixed coordinate spaces or conflicting updatesInspect parenting, transform spaces and update order
Slight lag or jitter during fast attack motionManual sync runs on a different frame step than the animation mixerEnsure any manual update runs after the animation mixer updates, or remove it entirely
Weapon scales oddly on some charactersRoot scale conflated with prop scaleCheck the attach output's own scale value separately from the character root's scale
Bind looks fine on one rig, wrong on anotherDifferent bone mapping, axes or proportionsInspect the actual target bone and fit on each rig

Different rigs and proportions legitimately need different local offsets — getting identical numbers across two characters is not a correctness requirement, and isn't something to chase. If you're fitting one weapon across multiple body sizes, see fitting the same weapon to different character sizes.

FAQ

Do I need to write my own quaternion math to attach a weapon?

No. Use the Three.js snippet exported by gripforge_attach; it already encodes position, rotation, rotationOrder, and the baseQuat correction for that bind.

What if my rig doesn't use a standard bone name like RightHand?

Bone-name matching supports common humanoid naming conventions, but custom or non-standard skeletons should be checked directly — confirm the bone your prop is parented to is the one you expect before trusting the bind.

Is this the same process as Unreal socket attachment?

Conceptually similar — a prop is placed relative to a bone/socket rather than world space — but the mechanics differ. For the Unreal side, see the socket alignment transfer checklist.

Where do I get the exact field names in the bind output?

The attachment documentation lists the bind transform fields; if you're retrieving assets through an agent workflow, the MCP docs describe how hosted versus local tooling deliver those files into your project.

Try it on your own prompt

GripForge turns a text prompt into rigged, animated, engine-ready game assets — from the browser, or from Claude Code and Cursor through MCP. The free plan includes Studio attaches and API trials every month.

Start free Browse 11,000+ community assets

More from the blog

Three.js Bone-Local Weapon Attachment: World vs Local Transform · GripForge