@pixiv/three-vrm-s
collider.html
- An example with a node collider
+ An example with a collider
+
+
+ inside-collider.html
+ An example with an inside collider
+
+
+ plane-collider.html
+ An example with a plane collider
loader-plugin.html
diff --git a/packages/three-vrm-springbone/examples/inside-collider.html b/packages/three-vrm-springbone/examples/inside-collider.html
new file mode 100644
index 000000000..5d47d4dca
--- /dev/null
+++ b/packages/three-vrm-springbone/examples/inside-collider.html
@@ -0,0 +1,166 @@
+
+
+
+
+
+ three-vrm-springbone example - inside-collider
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/three-vrm-springbone/examples/loader-plugin.html b/packages/three-vrm-springbone/examples/loader-plugin.html
index 45aef1431..70c5001b8 100644
--- a/packages/three-vrm-springbone/examples/loader-plugin.html
+++ b/packages/three-vrm-springbone/examples/loader-plugin.html
@@ -34,6 +34,7 @@
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { VRMSpringBoneLoaderPlugin } from "@pixiv/three-vrm-springbone";
+ import GUI from 'three/addons/libs/lil-gui.module.min.js';
// renderer
const renderer = new THREE.WebGLRenderer();
@@ -43,7 +44,7 @@
// camera
const camera = new THREE.PerspectiveCamera( 30.0, window.innerWidth / window.innerHeight, 0.1, 20.0 );
- camera.position.set( 0.0, 1.0, 5.0 );
+ camera.position.set( 0.0, 1.0, 10.0 );
// camera controls
const controls = new OrbitControls( camera, renderer.domElement );
@@ -54,6 +55,9 @@
// scene
const scene = new THREE.Scene();
+ const helperRoot = new THREE.Group();
+ scene.add( helperRoot );
+
// light
const light = new THREE.DirectionalLight( 0xffffff, Math.PI );
light.position.set( 1.0, 1.0, 1.0 ).normalize();
@@ -61,14 +65,13 @@
// gltf and vrm
let currentGltf = null;
- let currentSpringBoneManager = null;
const loader = new GLTFLoader();
const loaderPluginOptions = {
- jointHelperRoot: scene,
- colliderHelperRoot: scene,
+ jointHelperRoot: helperRoot,
+ colliderHelperRoot: helperRoot,
};
@@ -80,6 +83,13 @@
function load( url ) {
+ if ( currentGltf != null ) {
+
+ scene.remove( currentGltf.scene );
+ helperRoot.clear();
+
+ }
+
loader.crossOrigin = 'anonymous';
loader.load(
@@ -92,7 +102,7 @@
currentGltf = gltf;
- const springBoneManager = gltf.vrmSpringBoneManager;
+ clock.start();
},
@@ -104,7 +114,7 @@
}
- load( './models/cubes.gltf' );
+ load( './models/sphere-collider.gltf' );
// helpers
const gridHelper = new THREE.GridHelper( 10, 10 );
@@ -146,6 +156,29 @@
animate();
+ // gui
+ const gui = new GUI();
+
+ const params = {
+
+ model: 'sphere-collider.gltf',
+
+ };
+
+ const availableModels = [
+
+ 'sphere-collider.gltf',
+ 'inside-collider.gltf',
+ 'plane-collider.gltf',
+
+ ];
+
+ gui.add( params, 'model', availableModels ).onChange( ( value ) => {
+
+ load( `./models/${ value }` );
+
+ } );
+
// dnd handler
window.addEventListener( 'dragover', function ( event ) {
@@ -160,16 +193,16 @@
// read given file then convert it to blob url
const files = event.dataTransfer.files;
if ( ! files ) {
-
+
return;
-
+
}
const file = files[ 0 ];
if ( ! file ) {
-
+
return;
-
+
}
const blob = new Blob( [ file ], { type: "application/octet-stream" } );
diff --git a/packages/three-vrm-springbone/examples/models/cube_mesh.bin b/packages/three-vrm-springbone/examples/models/cube_mesh.bin
deleted file mode 100644
index 3f26e918e..000000000
Binary files a/packages/three-vrm-springbone/examples/models/cube_mesh.bin and /dev/null differ
diff --git a/packages/three-vrm-springbone/examples/models/inside-collider.gltf b/packages/three-vrm-springbone/examples/models/inside-collider.gltf
new file mode 100644
index 000000000..b2e3b0510
--- /dev/null
+++ b/packages/three-vrm-springbone/examples/models/inside-collider.gltf
@@ -0,0 +1,368 @@
+{
+ "asset": {
+ "generator": "Handwritten",
+ "version": "2.0"
+ },
+ "buffers": [
+ {
+ "uri": "sphere_mesh.bin",
+ "byteLength": 2496
+ }
+ ],
+ "bufferViews": [
+ {
+ "name": "Sphere Position",
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 756,
+ "target": 34962
+ },
+ {
+ "name": "Sphere Normal",
+ "buffer": 0,
+ "byteOffset": 756,
+ "byteLength": 756,
+ "target": 34962
+ },
+ {
+ "name": "Sphere UV",
+ "buffer": 0,
+ "byteOffset": 1512,
+ "byteLength": 504,
+ "target": 34962
+ },
+ {
+ "name": "Sphere Indices",
+ "buffer": 0,
+ "byteOffset": 2016,
+ "byteLength": 480,
+ "target": 34963
+ }
+ ],
+ "accessors": [
+ {
+ "name": "Sphere Position",
+ "bufferView": 0,
+ "type": "VEC3",
+ "componentType": 5126,
+ "count": 63,
+ "max": [
+ 0.9510578513145447,
+ 1,
+ 0.9999999403953552
+ ],
+ "min": [
+ -0.9510578513145447,
+ -1,
+ -0.9999999403953552
+ ]
+ },
+ {
+ "name": "Sphere Normal",
+ "bufferView": 1,
+ "type": "VEC3",
+ "componentType": 5126,
+ "count": 63
+ },
+ {
+ "name": "Sphere UV",
+ "bufferView": 2,
+ "type": "VEC2",
+ "componentType": 5126,
+ "count": 63
+ },
+ {
+ "name": "Sphere Indices",
+ "bufferView": 3,
+ "type": "SCALAR",
+ "componentType": 5123,
+ "count": 240
+ }
+ ],
+ "materials": [
+ {
+ "name": "Sphere Material Yellow",
+ "pbrMetallicRoughness": {
+ "baseColorFactor": [ 1, 1, 0.7, 1 ],
+ "metallicFactor": 0,
+ "roughnessFactor": 0.5
+ },
+ "alphaMode": "OPAQUE",
+ "doubleSided": false
+ },
+ {
+ "name": "Sphere Material Pink",
+ "pbrMetallicRoughness": {
+ "baseColorFactor": [ 1, 0.7, 1, 0.5 ],
+ "metallicFactor": 0,
+ "roughnessFactor": 0.5
+ },
+ "alphaMode": "BLEND",
+ "doubleSided": false
+ }
+ ],
+ "meshes": [
+ {
+ "name": "Sphere Yellow",
+ "primitives": [
+ {
+ "mode": 4,
+ "indices": 3,
+ "attributes": {
+ "POSITION": 0,
+ "NORMAL": 1,
+ "TEXCOORD_0": 2
+ },
+ "material": 0
+ }
+ ]
+ },
+ {
+ "name": "Sphere Pink",
+ "primitives": [
+ {
+ "mode": 4,
+ "indices": 3,
+ "attributes": {
+ "POSITION": 0,
+ "NORMAL": 1,
+ "TEXCOORD_0": 2
+ },
+ "material": 1
+ }
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "name": "Spring A",
+ "children": [
+ 1
+ ],
+ "translation": [
+ 0,
+ 2,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 0.25,
+ 0.25,
+ 0.25
+ ],
+ "mesh": 0,
+ "extras": {}
+ },
+ {
+ "name": "Spring B",
+ "children": [
+ 2
+ ],
+ "translation": [
+ 0,
+ -2,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "mesh": 0,
+ "extras": {}
+ },
+ {
+ "name": "Spring C",
+ "children": [
+ 3
+ ],
+ "translation": [
+ 0,
+ -2,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "mesh": 0,
+ "extras": {}
+ },
+ {
+ "name": "Spring D",
+ "children": [
+ 4
+ ],
+ "translation": [
+ 0,
+ -2,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "mesh": 0,
+ "extras": {}
+ },
+ {
+ "name": "Spring E",
+ "translation": [
+ 0,
+ -2,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "mesh": 0,
+ "extras": {}
+ },
+ {
+ "name": "Collider",
+ "translation": [
+ 0,
+ 1,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 1.35,
+ 1.35,
+ 1.35
+ ],
+ "mesh": 1,
+ "extras": {}
+ }
+ ],
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0,
+ 5
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "VRMC_springBone",
+ "VRMC_springBone_extended_collider"
+ ],
+ "extensions": {
+ "VRMC_springBone": {
+ "specVersion": "1.0",
+ "colliders": [
+ {
+ "node": 5,
+ "shape": {
+ "sphere": {
+ "offset": [
+ 10000.0,
+ 0.0,
+ 0.0
+ ],
+ "radius": 0.0
+ }
+ },
+ "extensions": {
+ "VRMC_springBone_extended_collider": {
+ "specVersion": "1.0-draft",
+ "shape": {
+ "sphere": {
+ "radius": 1.25,
+ "inside": true
+ }
+ }
+ }
+ }
+ }
+ ],
+ "colliderGroups": [
+ {
+ "colliders": [ 0 ]
+ }
+ ],
+ "springs": [
+ {
+ "joints": [
+ {
+ "node": 0,
+ "hitRadius": 0.25,
+ "stiffness": 1.0,
+ "gravityPower": 0.0,
+ "gravityDir": [ 0.0, -1.0, 0.0 ],
+ "dragForce": 0.4
+ },
+ {
+ "node": 1,
+ "hitRadius": 0.25,
+ "stiffness": 1.0,
+ "gravityPower": 0.0,
+ "gravityDir": [ 0.0, -1.0, 0.0 ],
+ "dragForce": 0.4
+ },
+ {
+ "node": 2,
+ "hitRadius": 0.25,
+ "stiffness": 1.0,
+ "gravityPower": 0.0,
+ "gravityDir": [ 0.0, -1.0, 0.0 ],
+ "dragForce": 0.4
+ },
+ {
+ "node": 3,
+ "hitRadius": 0.25,
+ "stiffness": 1.0,
+ "gravityPower": 0.0,
+ "gravityDir": [ 0.0, -1.0, 0.0 ],
+ "dragForce": 0.4
+ },
+ {
+ "node": 4
+ }
+ ],
+ "colliderGroups": [ 0 ]
+ }
+ ]
+ }
+ },
+ "extras": {}
+}
diff --git a/packages/three-vrm-springbone/examples/models/plane-collider.gltf b/packages/three-vrm-springbone/examples/models/plane-collider.gltf
new file mode 100644
index 000000000..41c536472
--- /dev/null
+++ b/packages/three-vrm-springbone/examples/models/plane-collider.gltf
@@ -0,0 +1,441 @@
+{
+ "asset": {
+ "generator": "Handwritten",
+ "version": "2.0"
+ },
+ "buffers": [
+ {
+ "uri": "sphere_mesh.bin",
+ "byteLength": 2496
+ },
+ {
+ "uri": "plane_mesh.bin",
+ "byteLength": 140
+ }
+ ],
+ "bufferViews": [
+ {
+ "name": "Sphere Position",
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 756,
+ "target": 34962
+ },
+ {
+ "name": "Sphere Normal",
+ "buffer": 0,
+ "byteOffset": 756,
+ "byteLength": 756,
+ "target": 34962
+ },
+ {
+ "name": "Sphere UV",
+ "buffer": 0,
+ "byteOffset": 1512,
+ "byteLength": 504,
+ "target": 34962
+ },
+ {
+ "name": "Sphere Indices",
+ "buffer": 0,
+ "byteOffset": 2016,
+ "byteLength": 480,
+ "target": 34963
+ },
+ {
+ "name": "Plane Position",
+ "buffer": 1,
+ "byteOffset": 0,
+ "byteLength": 48,
+ "target": 34962
+ },
+ {
+ "name": "Plane Normal",
+ "buffer": 1,
+ "byteOffset": 48,
+ "byteLength": 48,
+ "target": 34962
+ },
+ {
+ "name": "Plane UV",
+ "buffer": 1,
+ "byteOffset": 96,
+ "byteLength": 32,
+ "target": 34962
+ },
+ {
+ "name": "Plane Indices",
+ "buffer": 1,
+ "byteOffset": 128,
+ "byteLength": 12,
+ "target": 34963
+ }
+ ],
+ "accessors": [
+ {
+ "name": "Sphere Position",
+ "bufferView": 0,
+ "type": "VEC3",
+ "componentType": 5126,
+ "count": 63,
+ "max": [
+ 0.9510578513145447,
+ 1,
+ 0.9999999403953552
+ ],
+ "min": [
+ -0.9510578513145447,
+ -1,
+ -0.9999999403953552
+ ]
+ },
+ {
+ "name": "Sphere Normal",
+ "bufferView": 1,
+ "type": "VEC3",
+ "componentType": 5126,
+ "count": 63
+ },
+ {
+ "name": "Sphere UV",
+ "bufferView": 2,
+ "type": "VEC2",
+ "componentType": 5126,
+ "count": 63
+ },
+ {
+ "name": "Sphere Indices",
+ "bufferView": 3,
+ "type": "SCALAR",
+ "componentType": 5123,
+ "count": 240
+ },
+ {
+ "name": "Plane Position",
+ "bufferView": 4,
+ "type": "VEC3",
+ "componentType": 5126,
+ "count": 4,
+ "max":[
+ 0.9999999403953552,
+ 1,
+ 0
+ ],
+ "min":[
+ -0.9999999403953552,
+ -1,
+ 0
+ ]
+ },
+ {
+ "name": "Plane Normal",
+ "bufferView": 5,
+ "type": "VEC3",
+ "componentType": 5126,
+ "count": 4
+ },
+ {
+ "name": "Plane UV",
+ "bufferView": 6,
+ "type": "VEC2",
+ "componentType": 5126,
+ "count": 4
+ },
+ {
+ "name": "Plane Indices",
+ "bufferView": 7,
+ "type": "SCALAR",
+ "componentType": 5123,
+ "count": 6
+ }
+ ],
+ "materials": [
+ {
+ "name": "Sphere Material Yellow",
+ "pbrMetallicRoughness": {
+ "baseColorFactor": [ 1, 1, 0.7, 1 ],
+ "metallicFactor": 0,
+ "roughnessFactor": 0.5
+ },
+ "alphaMode": "OPAQUE",
+ "doubleSided": false
+ },
+ {
+ "name": "Plane Material Pink",
+ "pbrMetallicRoughness": {
+ "baseColorFactor": [ 1, 0.7, 1, 1 ],
+ "metallicFactor": 0,
+ "roughnessFactor": 0.5
+ },
+ "alphaMode": "OPAQUE",
+ "doubleSided": true
+ }
+ ],
+ "meshes": [
+ {
+ "name": "Cube",
+ "primitives": [
+ {
+ "mode": 4,
+ "indices": 3,
+ "attributes": {
+ "POSITION": 0,
+ "NORMAL": 1,
+ "TEXCOORD_0": 2
+ },
+ "material": 0
+ }
+ ]
+ },
+ {
+ "name": "Plane Pink",
+ "primitives": [
+ {
+ "mode": 4,
+ "indices": 7,
+ "attributes": {
+ "POSITION": 4,
+ "NORMAL": 5,
+ "TEXCOORD_0": 6
+ },
+ "material": 1
+ }
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "name": "Spring A",
+ "children": [
+ 1
+ ],
+ "translation": [
+ 0,
+ 2,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 0.25,
+ 0.25,
+ 0.25
+ ],
+ "mesh": 0,
+ "extras": {}
+ },
+ {
+ "name": "Spring B",
+ "children": [
+ 2
+ ],
+ "translation": [
+ 0,
+ -2,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "mesh": 0,
+ "extras": {}
+ },
+ {
+ "name": "Spring C",
+ "children": [
+ 3
+ ],
+ "translation": [
+ 0,
+ -2,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "mesh": 0,
+ "extras": {}
+ },
+ {
+ "name": "Spring D",
+ "children": [
+ 4
+ ],
+ "translation": [
+ 0,
+ -2,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "mesh": 0,
+ "extras": {}
+ },
+ {
+ "name": "Spring E",
+ "translation": [
+ 0,
+ -2,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "mesh": 0,
+ "extras": {}
+ },
+ {
+ "name": "Collider",
+ "translation": [
+ -0.25,
+ 1,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0.70710678118,
+ 0,
+ 0.70710678118
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "mesh": 1,
+ "extras": {}
+ }
+ ],
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0,
+ 5
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "VRMC_springBone",
+ "VRMC_springBone_extended_collider"
+ ],
+ "extensions": {
+ "VRMC_springBone": {
+ "specVersion": "1.0",
+ "colliders": [
+ {
+ "node": 5,
+ "shape": {
+ "sphere": {
+ "offset": [
+ -10000.0,
+ 0.0,
+ 0.0
+ ],
+ "radius": 10000.0
+ }
+ },
+ "extensions": {
+ "VRMC_springBone_extended_collider": {
+ "specVersion": "1.0-draft",
+ "shape": {
+ "plane": {
+ "normal": [
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ }
+ }
+ }
+ }
+ }
+ ],
+ "colliderGroups": [
+ {
+ "colliders": [ 0 ]
+ }
+ ],
+ "springs": [
+ {
+ "joints": [
+ {
+ "node": 0,
+ "hitRadius": 0.25,
+ "stiffness": 1.0,
+ "gravityPower": 0.0,
+ "gravityDir": [ 0.0, -1.0, 0.0 ],
+ "dragForce": 0.4
+ },
+ {
+ "node": 1,
+ "hitRadius": 0.25,
+ "stiffness": 1.0,
+ "gravityPower": 0.0,
+ "gravityDir": [ 0.0, -1.0, 0.0 ],
+ "dragForce": 0.4
+ },
+ {
+ "node": 2,
+ "hitRadius": 0.25,
+ "stiffness": 1.0,
+ "gravityPower": 0.0,
+ "gravityDir": [ 0.0, -1.0, 0.0 ],
+ "dragForce": 0.4
+ },
+ {
+ "node": 3,
+ "hitRadius": 0.25,
+ "stiffness": 1.0,
+ "gravityPower": 0.0,
+ "gravityDir": [ 0.0, -1.0, 0.0 ],
+ "dragForce": 0.4
+ },
+ {
+ "node": 4
+ }
+ ],
+ "colliderGroups": [ 0 ]
+ }
+ ]
+ }
+ },
+ "extras": {}
+}
diff --git a/packages/three-vrm-springbone/examples/models/plane_mesh.bin b/packages/three-vrm-springbone/examples/models/plane_mesh.bin
new file mode 100644
index 000000000..859ce947f
Binary files /dev/null and b/packages/three-vrm-springbone/examples/models/plane_mesh.bin differ
diff --git a/packages/three-vrm-springbone/examples/models/cubes.gltf b/packages/three-vrm-springbone/examples/models/sphere-collider.gltf
similarity index 68%
rename from packages/three-vrm-springbone/examples/models/cubes.gltf
rename to packages/three-vrm-springbone/examples/models/sphere-collider.gltf
index eb352b13d..b864452f1 100644
--- a/packages/three-vrm-springbone/examples/models/cubes.gltf
+++ b/packages/three-vrm-springbone/examples/models/sphere-collider.gltf
@@ -5,93 +5,95 @@
},
"buffers": [
{
- "uri": "cube_mesh.bin",
- "byteLength": 912
+ "uri": "sphere_mesh.bin",
+ "byteLength": 2496
}
],
"bufferViews": [
{
- "name": "Cube Position",
+ "name": "Sphere Position",
"buffer": 0,
"byteOffset": 0,
- "byteLength": 288,
+ "byteLength": 756,
"target": 34962
},
{
- "name": "Cube Normal",
+ "name": "Sphere Normal",
"buffer": 0,
- "byteOffset": 288,
- "byteLength": 288,
+ "byteOffset": 756,
+ "byteLength": 756,
"target": 34962
},
{
- "name": "Cube UV",
+ "name": "Sphere UV",
"buffer": 0,
- "byteOffset": 576,
- "byteLength": 192,
+ "byteOffset": 1512,
+ "byteLength": 504,
"target": 34962
},
{
- "name": "Cube Indices",
+ "name": "Sphere Indices",
"buffer": 0,
- "byteOffset": 768,
- "byteLength": 144,
+ "byteOffset": 2016,
+ "byteLength": 480,
"target": 34963
}
],
"accessors": [
{
- "name": "Cube Position",
+ "name": "Sphere Position",
"bufferView": 0,
- "byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
- "count": 24,
+ "count": 63,
"max": [
- 0.5,
- 0.5,
- 0.5
+ 0.9510578513145447,
+ 1,
+ 0.9999999403953552
],
"min": [
- -0.5,
- -0.5,
- -0.5
- ],
- "normalized": false
+ -0.9510578513145447,
+ -1,
+ -0.9999999403953552
+ ]
},
{
- "name": "Cube Normal",
+ "name": "Sphere Normal",
"bufferView": 1,
- "byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
- "count": 24,
- "normalized": false
+ "count": 63
},
{
- "name": "Cube UV",
+ "name": "Sphere UV",
"bufferView": 2,
- "byteOffset": 0,
"type": "VEC2",
"componentType": 5126,
- "count": 24,
- "normalized": false
+ "count": 63
},
{
- "name": "Cube Indices",
+ "name": "Sphere Indices",
"bufferView": 3,
- "byteOffset": 0,
"type": "SCALAR",
- "componentType": 5125,
- "count": 36,
- "normalized": false
+ "componentType": 5123,
+ "count": 240
}
],
"materials": [
{
- "name": "Cube Material",
+ "name": "Sphere Material Yellow",
+ "pbrMetallicRoughness": {
+ "baseColorFactor": [ 1, 1, 0.7, 1 ],
+ "metallicFactor": 0,
+ "roughnessFactor": 0.5
+ },
+ "alphaMode": "OPAQUE",
+ "doubleSided": false
+ },
+ {
+ "name": "Sphere Material Pink",
"pbrMetallicRoughness": {
- "baseColorFactor": [ 1, 1, 1, 1 ],
+ "baseColorFactor": [ 1, 0.7, 1, 1 ],
"metallicFactor": 0,
"roughnessFactor": 0.5
},
@@ -101,7 +103,7 @@
],
"meshes": [
{
- "name": "Cube",
+ "name": "Sphere Yellow",
"primitives": [
{
"mode": 4,
@@ -114,11 +116,26 @@
"material": 0
}
]
+ },
+ {
+ "name": "Sphere Pink",
+ "primitives": [
+ {
+ "mode": 4,
+ "indices": 3,
+ "attributes": {
+ "POSITION": 0,
+ "NORMAL": 1,
+ "TEXCOORD_0": 2
+ },
+ "material": 1
+ }
+ ]
}
],
"nodes": [
{
- "name": "CubeA",
+ "name": "Spring A",
"children": [
1
],
@@ -134,21 +151,21 @@
1
],
"scale": [
- 0.5,
- 0.5,
- 0.5
+ 0.25,
+ 0.25,
+ 0.25
],
"mesh": 0,
"extras": {}
},
{
- "name": "CubeB",
+ "name": "Spring B",
"children": [
2
],
"translation": [
0,
- -1,
+ -2,
0
],
"rotation": [
@@ -166,13 +183,13 @@
"extras": {}
},
{
- "name": "CubeC",
+ "name": "Spring C",
"children": [
3
],
"translation": [
0,
- -1,
+ -2,
0
],
"rotation": [
@@ -190,13 +207,13 @@
"extras": {}
},
{
- "name": "CubeD",
+ "name": "Spring D",
"children": [
4
],
"translation": [
0,
- -1,
+ -2,
0
],
"rotation": [
@@ -214,10 +231,10 @@
"extras": {}
},
{
- "name": "CubeE",
+ "name": "Spring E",
"translation": [
0,
- -1,
+ -2,
0
],
"rotation": [
@@ -237,7 +254,7 @@
{
"name": "Collider",
"translation": [
- 0.5,
+ 0.75,
1,
0
],
@@ -248,10 +265,11 @@
1
],
"scale": [
- 1,
- 1,
- 1
+ 0.5,
+ 0.5,
+ 0.5
],
+ "mesh": 1,
"extras": {}
}
],
@@ -293,9 +311,17 @@
"springs": [
{
"joints": [
+ {
+ "node": 0,
+ "hitRadius": 0.25,
+ "stiffness": 1.0,
+ "gravityPower": 0.0,
+ "gravityDir": [ 0.0, -1.0, 0.0 ],
+ "dragForce": 0.4
+ },
{
"node": 1,
- "hitRadius": 0.02,
+ "hitRadius": 0.25,
"stiffness": 1.0,
"gravityPower": 0.0,
"gravityDir": [ 0.0, -1.0, 0.0 ],
@@ -303,7 +329,7 @@
},
{
"node": 2,
- "hitRadius": 0.02,
+ "hitRadius": 0.25,
"stiffness": 1.0,
"gravityPower": 0.0,
"gravityDir": [ 0.0, -1.0, 0.0 ],
@@ -311,7 +337,7 @@
},
{
"node": 3,
- "hitRadius": 0.02,
+ "hitRadius": 0.25,
"stiffness": 1.0,
"gravityPower": 0.0,
"gravityDir": [ 0.0, -1.0, 0.0 ],
diff --git a/packages/three-vrm-springbone/examples/models/sphere_mesh.bin b/packages/three-vrm-springbone/examples/models/sphere_mesh.bin
new file mode 100644
index 000000000..4ff55c723
Binary files /dev/null and b/packages/three-vrm-springbone/examples/models/sphere_mesh.bin differ
diff --git a/packages/three-vrm-springbone/examples/plane-collider.html b/packages/three-vrm-springbone/examples/plane-collider.html
new file mode 100644
index 000000000..99e87f668
--- /dev/null
+++ b/packages/three-vrm-springbone/examples/plane-collider.html
@@ -0,0 +1,164 @@
+
+
+
+
+
+ three-vrm-springbone example - plane-collider
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/three-vrm-springbone/package.json b/packages/three-vrm-springbone/package.json
index 5a1d3acbf..8d0813f7d 100644
--- a/packages/three-vrm-springbone/package.json
+++ b/packages/three-vrm-springbone/package.json
@@ -52,7 +52,8 @@
},
"dependencies": {
"@pixiv/types-vrm-0.0": "3.0.0",
- "@pixiv/types-vrmc-springbone-1.0": "3.0.0"
+ "@pixiv/types-vrmc-springbone-1.0": "3.0.0",
+ "@pixiv/types-vrmc-springbone-extended-collider-1.0": "3.0.0"
},
"devDependencies": {
"@types/three": "^0.167.0",
diff --git a/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapeCapsule.ts b/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapeCapsule.ts
index 8135e11cb..1d6aa51a0 100644
--- a/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapeCapsule.ts
+++ b/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapeCapsule.ts
@@ -10,26 +10,32 @@ export class VRMSpringBoneColliderShapeCapsule extends VRMSpringBoneColliderShap
}
/**
- * The offset of the head from the origin.
+ * The offset of the capsule head from the origin in local space.
*/
public offset: THREE.Vector3;
/**
- * The offset of the tail from the origin.
+ * The offset of the capsule tail from the origin in local space.
*/
public tail: THREE.Vector3;
/**
- * The radius.
+ * The radius of the capsule.
*/
public radius: number;
- public constructor(params?: { radius?: number; offset?: THREE.Vector3; tail?: THREE.Vector3 }) {
+ /**
+ * If true, the collider prevents spring bones from going outside of the capsule instead.
+ */
+ public inside: boolean;
+
+ public constructor(params?: { radius?: number; offset?: THREE.Vector3; tail?: THREE.Vector3; inside?: boolean }) {
super();
this.offset = params?.offset ?? new THREE.Vector3(0.0, 0.0, 0.0);
this.tail = params?.tail ?? new THREE.Vector3(0.0, 0.0, 0.0);
this.radius = params?.radius ?? 0.0;
+ this.inside = params?.inside ?? false;
}
public calculateCollision(
@@ -58,9 +64,15 @@ export class VRMSpringBoneColliderShapeCapsule extends VRMSpringBoneColliderShap
target.sub(_v3B); // from the shaft point to object
}
- const radius = objectRadius + this.radius;
- const distance = target.length() - radius;
- target.normalize();
+ const distance = this.inside
+ ? this.radius - objectRadius - target.length()
+ : target.length() - objectRadius - this.radius;
+
+ target.normalize(); // convert the delta to the direction
+ if (this.inside) {
+ target.negate(); // if inside, reverse the direction
+ }
+
return distance;
}
}
diff --git a/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapePlane.ts b/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapePlane.ts
new file mode 100644
index 000000000..087d1fd26
--- /dev/null
+++ b/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapePlane.ts
@@ -0,0 +1,46 @@
+import * as THREE from 'three';
+import { VRMSpringBoneColliderShape } from './VRMSpringBoneColliderShape';
+
+const _v3A = new THREE.Vector3();
+const _mat3A = new THREE.Matrix3();
+
+export class VRMSpringBoneColliderShapePlane extends VRMSpringBoneColliderShape {
+ public get type(): 'plane' {
+ return 'plane';
+ }
+
+ /**
+ * The offset of the plane from the origin in local space.
+ */
+ public offset: THREE.Vector3;
+
+ /**
+ * The normal of the plane in local space. Must be normalized.
+ */
+ public normal: THREE.Vector3;
+
+ public constructor(params?: { offset?: THREE.Vector3; normal?: THREE.Vector3 }) {
+ super();
+
+ this.offset = params?.offset ?? new THREE.Vector3(0.0, 0.0, 0.0);
+ this.normal = params?.normal ?? new THREE.Vector3(0.0, 0.0, 1.0);
+ }
+
+ public calculateCollision(
+ colliderMatrix: THREE.Matrix4,
+ objectPosition: THREE.Vector3,
+ objectRadius: number,
+ target: THREE.Vector3,
+ ): number {
+ target.copy(this.offset).applyMatrix4(colliderMatrix); // transformed offset
+ target.negate().add(objectPosition); // a vector from collider center to object position
+
+ _mat3A.getNormalMatrix(colliderMatrix); // convert the collider matrix to the normal matrix
+ _v3A.copy(this.normal).applyNormalMatrix(_mat3A).normalize(); // transformed normal
+ const distance = target.dot(_v3A) - objectRadius;
+
+ target.copy(_v3A); // convert the delta to the direction
+
+ return distance;
+ }
+}
diff --git a/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapeSphere.ts b/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapeSphere.ts
index b54d20778..95aac74d1 100644
--- a/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapeSphere.ts
+++ b/packages/three-vrm-springbone/src/VRMSpringBoneColliderShapeSphere.ts
@@ -7,7 +7,7 @@ export class VRMSpringBoneColliderShapeSphere extends VRMSpringBoneColliderShape
}
/**
- * The offset from the origin.
+ * The offset of the sphere from the origin in local space.
*/
public offset: THREE.Vector3;
@@ -16,11 +16,17 @@ export class VRMSpringBoneColliderShapeSphere extends VRMSpringBoneColliderShape
*/
public radius: number;
- public constructor(params?: { radius?: number; offset?: THREE.Vector3 }) {
+ /**
+ * If true, the collider prevents spring bones from going outside of the sphere instead.
+ */
+ public inside: boolean;
+
+ public constructor(params?: { radius?: number; offset?: THREE.Vector3; inside?: boolean }) {
super();
this.offset = params?.offset ?? new THREE.Vector3(0.0, 0.0, 0.0);
this.radius = params?.radius ?? 0.0;
+ this.inside = params?.inside ?? false;
}
public calculateCollision(
@@ -31,9 +37,16 @@ export class VRMSpringBoneColliderShapeSphere extends VRMSpringBoneColliderShape
): number {
target.copy(this.offset).applyMatrix4(colliderMatrix); // transformed offset
target.negate().add(objectPosition); // a vector from collider center to object position
- const radius = objectRadius + this.radius;
- const distance = target.length() - radius;
- target.normalize();
+
+ const distance = this.inside
+ ? this.radius - objectRadius - target.length()
+ : target.length() - objectRadius - this.radius;
+
+ target.normalize(); // convert the delta to the direction
+ if (this.inside) {
+ target.negate(); // if inside, reverse the direction
+ }
+
return distance;
}
}
diff --git a/packages/three-vrm-springbone/src/VRMSpringBoneLoaderPlugin.ts b/packages/three-vrm-springbone/src/VRMSpringBoneLoaderPlugin.ts
index 1e2c3eaca..121915fc4 100644
--- a/packages/three-vrm-springbone/src/VRMSpringBoneLoaderPlugin.ts
+++ b/packages/three-vrm-springbone/src/VRMSpringBoneLoaderPlugin.ts
@@ -1,5 +1,6 @@
import type * as V0VRM from '@pixiv/types-vrm-0.0';
import type * as V1SpringBoneSchema from '@pixiv/types-vrmc-springbone-1.0';
+import type * as SpringBoneExtendedColliderSchema from '@pixiv/types-vrmc-springbone-extended-collider-1.0';
import * as THREE from 'three';
import type { GLTF, GLTFLoaderPlugin, GLTFParser } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { VRMSpringBoneColliderHelper, VRMSpringBoneJointHelper } from './helpers';
@@ -12,12 +13,20 @@ import type { VRMSpringBoneLoaderPluginOptions } from './VRMSpringBoneLoaderPlug
import { VRMSpringBoneManager } from './VRMSpringBoneManager';
import type { VRMSpringBoneJointSettings } from './VRMSpringBoneJointSettings';
import { GLTF as GLTFSchema } from '@gltf-transform/core';
+import { VRMSpringBoneColliderShapePlane } from './VRMSpringBoneColliderShapePlane';
+
+const EXTENSION_NAME_EXTENDED_COLLIDER = 'VRMC_springBone_extended_collider';
/**
* Possible spec versions it recognizes.
*/
const POSSIBLE_SPEC_VERSIONS = new Set(['1.0', '1.0-beta']);
+/**
+ * Possible spec versions of `VRMC_springBone_extended_collider` it recognizes.
+ */
+const POSSIBLE_SPEC_VERSIONS_EXTENDED_COLLIDERS = new Set(['1.0-draft']);
+
export class VRMSpringBoneLoaderPlugin implements GLTFLoaderPlugin {
public static readonly EXTENSION_NAME = 'VRMC_springBone';
@@ -35,6 +44,13 @@ export class VRMSpringBoneLoaderPlugin implements GLTFLoaderPlugin {
*/
public colliderHelperRoot?: THREE.Object3D;
+ /**
+ * If true, load colliders defined in `VRMC_springBone_extended_collider`.
+ * Set to `false` to disable loading extended colliders and use the fallback behavior.
+ * `true` by default.
+ */
+ public useExtendedColliders: boolean;
+
public readonly parser: GLTFParser;
public get name(): string {
@@ -46,6 +62,7 @@ export class VRMSpringBoneLoaderPlugin implements GLTFLoaderPlugin {
this.jointHelperRoot = options?.jointHelperRoot;
this.colliderHelperRoot = options?.colliderHelperRoot;
+ this.useExtendedColliders = options?.useExtendedColliders ?? true;
}
public async afterRoot(gltf: GLTF): Promise {
@@ -104,16 +121,53 @@ export class VRMSpringBoneLoaderPlugin implements GLTFLoaderPlugin {
const node = threeNodes[schemaCollider.node!];
const schemaShape = schemaCollider.shape!;
+ // TODO: separate into several functions
+
+ const schemaExCollider: SpringBoneExtendedColliderSchema.VRMCSpringBoneExtendedCollider | undefined =
+ schemaCollider.extensions?.[EXTENSION_NAME_EXTENDED_COLLIDER];
+
+ if (this.useExtendedColliders && schemaExCollider != null) {
+ const specVersionExCollider = schemaExCollider.specVersion;
+ if (!POSSIBLE_SPEC_VERSIONS_EXTENDED_COLLIDERS.has(specVersionExCollider)) {
+ console.warn(
+ `VRMSpringBoneLoaderPlugin: Unknown ${EXTENSION_NAME_EXTENDED_COLLIDER} specVersion "${specVersionExCollider}". Fallbacking to the ${VRMSpringBoneLoaderPlugin.EXTENSION_NAME} definition`,
+ );
+ } else {
+ const schemaExShape = schemaExCollider.shape!;
+ if (schemaExShape.sphere) {
+ return this._importSphereCollider(node, {
+ offset: new THREE.Vector3().fromArray(schemaExShape.sphere.offset ?? [0.0, 0.0, 0.0]),
+ radius: schemaExShape.sphere.radius ?? 0.0,
+ inside: schemaExShape.sphere.inside ?? false,
+ });
+ } else if (schemaExShape.capsule) {
+ return this._importCapsuleCollider(node, {
+ offset: new THREE.Vector3().fromArray(schemaExShape.capsule.offset ?? [0.0, 0.0, 0.0]),
+ radius: schemaExShape.capsule.radius ?? 0.0,
+ tail: new THREE.Vector3().fromArray(schemaExShape.capsule.tail ?? [0.0, 0.0, 0.0]),
+ inside: schemaExShape.capsule.inside ?? false,
+ });
+ } else if (schemaExShape.plane) {
+ return this._importPlaneCollider(node, {
+ offset: new THREE.Vector3().fromArray(schemaExShape.plane.offset ?? [0.0, 0.0, 0.0]),
+ normal: new THREE.Vector3().fromArray(schemaExShape.plane.normal ?? [0.0, 0.0, 1.0]),
+ });
+ }
+ }
+ }
+
if (schemaShape.sphere) {
return this._importSphereCollider(node, {
offset: new THREE.Vector3().fromArray(schemaShape.sphere.offset ?? [0.0, 0.0, 0.0]),
radius: schemaShape.sphere.radius ?? 0.0,
+ inside: false,
});
} else if (schemaShape.capsule) {
return this._importCapsuleCollider(node, {
offset: new THREE.Vector3().fromArray(schemaShape.capsule.offset ?? [0.0, 0.0, 0.0]),
radius: schemaShape.capsule.radius ?? 0.0,
tail: new THREE.Vector3().fromArray(schemaShape.capsule.tail ?? [0.0, 0.0, 0.0]),
+ inside: false,
});
}
@@ -240,6 +294,7 @@ export class VRMSpringBoneLoaderPlugin implements GLTFLoaderPlugin {
return this._importSphereCollider(node, {
offset,
radius: schemaCollider.radius ?? 0.0,
+ inside: false,
});
});
@@ -335,11 +390,10 @@ export class VRMSpringBoneLoaderPlugin implements GLTFLoaderPlugin {
params: {
offset: THREE.Vector3;
radius: number;
+ inside: boolean;
},
): VRMSpringBoneCollider {
- const { offset, radius } = params;
-
- const shape = new VRMSpringBoneColliderShapeSphere({ offset, radius });
+ const shape = new VRMSpringBoneColliderShapeSphere(params);
const collider = new VRMSpringBoneCollider(shape);
@@ -360,11 +414,32 @@ export class VRMSpringBoneLoaderPlugin implements GLTFLoaderPlugin {
offset: THREE.Vector3;
radius: number;
tail: THREE.Vector3;
+ inside: boolean;
},
): VRMSpringBoneCollider {
- const { offset, radius, tail } = params;
+ const shape = new VRMSpringBoneColliderShapeCapsule(params);
+
+ const collider = new VRMSpringBoneCollider(shape);
+
+ destination.add(collider);
+
+ if (this.colliderHelperRoot) {
+ const helper = new VRMSpringBoneColliderHelper(collider);
+ this.colliderHelperRoot.add(helper);
+ helper.renderOrder = this.colliderHelperRoot.renderOrder;
+ }
- const shape = new VRMSpringBoneColliderShapeCapsule({ offset, radius, tail });
+ return collider;
+ }
+
+ private _importPlaneCollider(
+ destination: THREE.Object3D,
+ params: {
+ offset: THREE.Vector3;
+ normal: THREE.Vector3;
+ },
+ ): VRMSpringBoneCollider {
+ const shape = new VRMSpringBoneColliderShapePlane(params);
const collider = new VRMSpringBoneCollider(shape);
diff --git a/packages/three-vrm-springbone/src/VRMSpringBoneLoaderPluginOptions.ts b/packages/three-vrm-springbone/src/VRMSpringBoneLoaderPluginOptions.ts
index a5fe05972..be91436b0 100644
--- a/packages/three-vrm-springbone/src/VRMSpringBoneLoaderPluginOptions.ts
+++ b/packages/three-vrm-springbone/src/VRMSpringBoneLoaderPluginOptions.ts
@@ -14,4 +14,11 @@ export interface VRMSpringBoneLoaderPluginOptions {
* If `renderOrder` is set to the root, helpers will copy the same `renderOrder` .
*/
colliderHelperRoot?: THREE.Object3D;
+
+ /**
+ * If true, load colliders defined in `VRMC_springBone_extended_collider`.
+ * Set to `false` to disable loading extended colliders and use the fallback behavior.
+ * `true` by default.
+ */
+ useExtendedColliders?: boolean;
}
diff --git a/packages/three-vrm-springbone/src/helpers/VRMSpringBoneColliderHelper.ts b/packages/three-vrm-springbone/src/helpers/VRMSpringBoneColliderHelper.ts
index b44f90cbe..c7182c067 100644
--- a/packages/three-vrm-springbone/src/helpers/VRMSpringBoneColliderHelper.ts
+++ b/packages/three-vrm-springbone/src/helpers/VRMSpringBoneColliderHelper.ts
@@ -1,9 +1,11 @@
import * as THREE from 'three';
import { VRMSpringBoneCollider } from '../VRMSpringBoneCollider';
import { VRMSpringBoneColliderShapeCapsule } from '../VRMSpringBoneColliderShapeCapsule';
+import { VRMSpringBoneColliderShapePlane } from '../VRMSpringBoneColliderShapePlane';
import { VRMSpringBoneColliderShapeSphere } from '../VRMSpringBoneColliderShapeSphere';
import { ColliderShapeBufferGeometry } from './utils/ColliderShapeBufferGeometry';
import { ColliderShapeCapsuleBufferGeometry } from './utils/ColliderShapeCapsuleBufferGeometry';
+import { ColliderShapePlaneBufferGeometry } from './utils/ColliderShapePlaneBufferGeometry';
import { ColliderShapeSphereBufferGeometry } from './utils/ColliderShapeSphereBufferGeometry';
const _v3A = new THREE.Vector3();
@@ -23,6 +25,8 @@ export class VRMSpringBoneColliderHelper extends THREE.Group {
this._geometry = new ColliderShapeSphereBufferGeometry(this.collider.shape);
} else if (this.collider.shape instanceof VRMSpringBoneColliderShapeCapsule) {
this._geometry = new ColliderShapeCapsuleBufferGeometry(this.collider.shape);
+ } else if (this.collider.shape instanceof VRMSpringBoneColliderShapePlane) {
+ this._geometry = new ColliderShapePlaneBufferGeometry(this.collider.shape);
} else {
throw new Error('VRMSpringBoneColliderHelper: Unknown collider shape type detected');
}
diff --git a/packages/three-vrm-springbone/src/helpers/utils/ColliderShapePlaneBufferGeometry.ts b/packages/three-vrm-springbone/src/helpers/utils/ColliderShapePlaneBufferGeometry.ts
new file mode 100644
index 000000000..98f52eab8
--- /dev/null
+++ b/packages/three-vrm-springbone/src/helpers/utils/ColliderShapePlaneBufferGeometry.ts
@@ -0,0 +1,70 @@
+import * as THREE from 'three';
+import { VRMSpringBoneColliderShapePlane } from '../../VRMSpringBoneColliderShapePlane';
+import { ColliderShapeBufferGeometry } from './ColliderShapeBufferGeometry';
+
+export class ColliderShapePlaneBufferGeometry extends THREE.BufferGeometry implements ColliderShapeBufferGeometry {
+ public worldScale = 1.0;
+
+ private readonly _attrPos: THREE.BufferAttribute;
+ private readonly _attrIndex: THREE.BufferAttribute;
+ private readonly _shape: VRMSpringBoneColliderShapePlane;
+ private readonly _currentOffset = new THREE.Vector3();
+ private readonly _currentNormal = new THREE.Vector3();
+
+ public constructor(shape: VRMSpringBoneColliderShapePlane) {
+ super();
+
+ this._shape = shape;
+
+ this._attrPos = new THREE.BufferAttribute(new Float32Array(6 * 3), 3);
+ this.setAttribute('position', this._attrPos);
+
+ this._attrIndex = new THREE.BufferAttribute(new Uint16Array(10), 1);
+ this.setIndex(this._attrIndex);
+
+ this._buildIndex();
+ this.update();
+ }
+
+ public update(): void {
+ let shouldUpdateGeometry = false;
+
+ if (!this._currentOffset.equals(this._shape.offset)) {
+ this._currentOffset.copy(this._shape.offset);
+ shouldUpdateGeometry = true;
+ }
+
+ if (!this._currentNormal.equals(this._shape.normal)) {
+ this._currentNormal.copy(this._shape.normal);
+ shouldUpdateGeometry = true;
+ }
+
+ if (shouldUpdateGeometry) {
+ this._buildPosition();
+ }
+ }
+
+ private _buildPosition(): void {
+ this._attrPos.setXYZ(0, -0.5, -0.5, 0);
+ this._attrPos.setXYZ(1, 0.5, -0.5, 0);
+ this._attrPos.setXYZ(2, 0.5, 0.5, 0);
+ this._attrPos.setXYZ(3, -0.5, 0.5, 0);
+ this._attrPos.setXYZ(4, 0, 0, 0);
+ this._attrPos.setXYZ(5, 0, 0, 0.25);
+
+ this.translate(this._currentOffset.x, this._currentOffset.y, this._currentOffset.z);
+ this.lookAt(this._currentNormal);
+
+ this._attrPos.needsUpdate = true;
+ }
+
+ private _buildIndex(): void {
+ this._attrIndex.setXY(0, 0, 1);
+ this._attrIndex.setXY(2, 1, 2);
+ this._attrIndex.setXY(4, 2, 3);
+ this._attrIndex.setXY(6, 3, 0);
+ this._attrIndex.setXY(8, 4, 5);
+
+ this._attrIndex.needsUpdate = true;
+ }
+}
diff --git a/packages/three-vrm-springbone/src/index.ts b/packages/three-vrm-springbone/src/index.ts
index 62b73923d..d1535d009 100644
--- a/packages/three-vrm-springbone/src/index.ts
+++ b/packages/three-vrm-springbone/src/index.ts
@@ -3,6 +3,7 @@ export * from './VRMSpringBoneCollider';
export * from './VRMSpringBoneColliderGroup';
export * from './VRMSpringBoneColliderShape';
export * from './VRMSpringBoneColliderShapeCapsule';
+export * from './VRMSpringBoneColliderShapePlane';
export * from './VRMSpringBoneColliderShapeSphere';
export * from './VRMSpringBoneJoint';
export * from './VRMSpringBoneLoaderPlugin';
diff --git a/packages/types-vrmc-springbone-extended-collider-1.0/.gitignore b/packages/types-vrmc-springbone-extended-collider-1.0/.gitignore
new file mode 100644
index 000000000..1509aadd3
--- /dev/null
+++ b/packages/types-vrmc-springbone-extended-collider-1.0/.gitignore
@@ -0,0 +1 @@
+!types
\ No newline at end of file
diff --git a/packages/types-vrmc-springbone-extended-collider-1.0/README.md b/packages/types-vrmc-springbone-extended-collider-1.0/README.md
new file mode 100644
index 000000000..52705919e
--- /dev/null
+++ b/packages/types-vrmc-springbone-extended-collider-1.0/README.md
@@ -0,0 +1,13 @@
+# @pixiv/types-vrmc-springbone-extended-collider-1.0
+
+Type definitions of VRMC_springBone_extended_collider 1.0 schema.
+
+https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_springBone_extended_collider-1.0
+
+There should not be any implementation in this package. Just type definitions of the schema.
+
+The extension root is named `VRMCSpringBoneExtendedCollider` .
+
+[GitHub Repository](https://github.com/pixiv/three-vrm/tree/dev/packages/types-vrmc-springbone-extended-collider-1.0)
+
+[Documentation](https://pixiv.github.io/three-vrm/packages/types-vrmc-springbone-extended-collider-1.0/docs)
diff --git a/packages/types-vrmc-springbone-extended-collider-1.0/package.json b/packages/types-vrmc-springbone-extended-collider-1.0/package.json
new file mode 100644
index 000000000..b625735d4
--- /dev/null
+++ b/packages/types-vrmc-springbone-extended-collider-1.0/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@pixiv/types-vrmc-springbone-extended-collider-1.0",
+ "version": "3.0.0",
+ "description": "Type definitions of VRMC_springBone_extended_collider 1.0 schema",
+ "license": "MIT",
+ "author": "pixiv",
+ "files": [
+ "/types/",
+ "LICENSE"
+ ],
+ "main": "",
+ "types": "types/index.d.ts",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/pixiv/three-vrm.git",
+ "directory": "packages/types-vrmc-springbone-extended-collider-1.0"
+ },
+ "scripts": {
+ "version": "yarn all",
+ "all": "yarn lint && yarn clean && yarn docs",
+ "clean": "rimraf docs/",
+ "docs": "typedoc --entryPoints ./types/index.d.ts --out docs",
+ "lint": "eslint \"types/**/*.{ts,tsx}\" && prettier \"types/**/*.{ts,tsx}\" --check",
+ "lint-fix": "eslint \"types/**/*.{ts,tsx}\" --fix && prettier \"types/**/*.{ts,tsx}\" --write"
+ },
+ "lint-staged": {
+ "./types/**/*.{ts,tsx}": [
+ "eslint --fix",
+ "prettier --write"
+ ]
+ }
+}
diff --git a/packages/types-vrmc-springbone-extended-collider-1.0/tsconfig.json b/packages/types-vrmc-springbone-extended-collider-1.0/tsconfig.json
new file mode 100644
index 000000000..15b9f2152
--- /dev/null
+++ b/packages/types-vrmc-springbone-extended-collider-1.0/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": [
+ "./types"
+ ]
+}
diff --git a/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShape.d.ts b/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShape.d.ts
new file mode 100644
index 000000000..6d8f11397
--- /dev/null
+++ b/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShape.d.ts
@@ -0,0 +1,15 @@
+import type { SpringBoneExtendedColliderShapeCapsule } from './SpringBoneExtendedColliderShapeCapsule';
+import type { SpringBoneExtendedColliderShapePlane } from './SpringBoneExtendedColliderShapePlane';
+import type { SpringBoneExtendedColliderShapeSphere } from './SpringBoneExtendedColliderShapeSphere';
+
+/**
+ * The shape of the collider. One of sphere, capsule, or plane is defined.
+ */
+export interface SpringBoneExtendedColliderShape {
+ sphere?: SpringBoneExtendedColliderShapeSphere;
+ capsule?: SpringBoneExtendedColliderShapeCapsule;
+ plane?: SpringBoneExtendedColliderShapePlane;
+
+ extensions?: { [name: string]: any };
+ extras?: any;
+}
diff --git a/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShapeCapsule.d.ts b/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShapeCapsule.d.ts
new file mode 100644
index 000000000..017026612
--- /dev/null
+++ b/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShapeCapsule.d.ts
@@ -0,0 +1,21 @@
+export interface SpringBoneExtendedColliderShapeCapsule {
+ /**
+ * The offset of the capsule head from the origin in local space.
+ */
+ offset?: [number, number, number];
+
+ /**
+ * The radius of the capsule.
+ */
+ radius?: number;
+
+ /**
+ * The offset of the capsule tail from the origin in local space.
+ */
+ tail?: [number, number, number];
+
+ /**
+ * If true, the collider prevents spring bones from going outside of the capsule instead.
+ */
+ inside?: boolean;
+}
diff --git a/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShapePlane.d.ts b/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShapePlane.d.ts
new file mode 100644
index 000000000..c42d2cb34
--- /dev/null
+++ b/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShapePlane.d.ts
@@ -0,0 +1,11 @@
+export interface SpringBoneExtendedColliderShapePlane {
+ /**
+ * The offset of the plane from the origin in local space.
+ */
+ offset?: [number, number, number];
+
+ /**
+ * The normal of the plane in local space. Must be normalized.
+ */
+ normal?: [number, number, number];
+}
diff --git a/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShapeSphere.d.ts b/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShapeSphere.d.ts
new file mode 100644
index 000000000..66b413698
--- /dev/null
+++ b/packages/types-vrmc-springbone-extended-collider-1.0/types/SpringBoneExtendedColliderShapeSphere.d.ts
@@ -0,0 +1,16 @@
+export interface SpringBoneExtendedColliderShapeSphere {
+ /**
+ * The offset of the sphere from the origin in local space.
+ */
+ offset?: [number, number, number];
+
+ /**
+ * The radius of the sphere.
+ */
+ radius?: number;
+
+ /**
+ * If true, the collider prevents spring bones from going outside of the sphere instead.
+ */
+ inside?: boolean;
+}
diff --git a/packages/types-vrmc-springbone-extended-collider-1.0/types/VRMCSpringBoneExtendedCollider.d.ts b/packages/types-vrmc-springbone-extended-collider-1.0/types/VRMCSpringBoneExtendedCollider.d.ts
new file mode 100644
index 000000000..db0d26e9e
--- /dev/null
+++ b/packages/types-vrmc-springbone-extended-collider-1.0/types/VRMCSpringBoneExtendedCollider.d.ts
@@ -0,0 +1,19 @@
+import type { SpringBoneExtendedColliderShape } from './SpringBoneExtendedColliderShape';
+
+/**
+ * An extended collider for VRMC_springBone.
+ */
+export interface VRMCSpringBoneExtendedCollider {
+ /**
+ * Specification version of VRMC_springBone_extended_collider.
+ */
+ specVersion: '1.0-draft';
+
+ /**
+ * The shape of the collider.
+ */
+ shape?: SpringBoneExtendedColliderShape;
+
+ extensions?: { [name: string]: any };
+ extras?: any;
+}
diff --git a/packages/types-vrmc-springbone-extended-collider-1.0/types/index.d.ts b/packages/types-vrmc-springbone-extended-collider-1.0/types/index.d.ts
new file mode 100644
index 000000000..0442459e7
--- /dev/null
+++ b/packages/types-vrmc-springbone-extended-collider-1.0/types/index.d.ts
@@ -0,0 +1,5 @@
+export type { SpringBoneExtendedColliderShape } from './SpringBoneExtendedColliderShape';
+export type { SpringBoneExtendedColliderShapeCapsule } from './SpringBoneExtendedColliderShapeCapsule';
+export type { SpringBoneExtendedColliderShapeSphere } from './SpringBoneExtendedColliderShapeSphere';
+export type { SpringBoneExtendedColliderShapePlane } from './SpringBoneExtendedColliderShapePlane';
+export type { VRMCSpringBoneExtendedCollider } from './VRMCSpringBoneExtendedCollider';