How to use Threejs to create a boundary for the Mesh load by fbxloador - three.js

The created boundary's scale and rotation are totally different with the import fbxmodel.
Hi, I have loaded a fbx model into the scene by fbxLoador.
const addFbxModel = (modelName: string, position: Vector3) => {
const fbxLoader = new FBXLoader();
fbxLoader.load(
`../../src/assets/models/${modelName}.fbx`,
(fbxObject: Object3D) => {
fbxObject.position.set(position.x, position.y, position.z);
console.log(fbxObject.scale);
fbxObject.scale.set(0.01, 0.01, 0.01);
const material = new MeshBasicMaterial({ color: 0x008080 });
fbxObject.traverse((object) => {
if (object instanceof Mesh) {
object.material = material;
}
});
scene.add(fbxObject);
updateRenderer();
updateCamera();
render();
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
(error) => {
console.log(error);
}
);
};
And now i want to add a click function on this model which will highlight it by showing it's boundary box.
const onclick = (event: MouseEvent) => {
event.preventDefault();
if (renderBox.value) {
const mouse = new Vector2();
mouse.x = (event.offsetX / renderBox.value.clientWidth) * 2 - 1;
mouse.y = -(event.offsetY / renderBox.value.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
const intersect = intersects[0];
const object = intersect.object;
createBoundary(object);
}
}
};
const createBoundary = (object: Object3D) => {
if (object instanceof Mesh) {
console.log(object.geometry, object.scale, object.rotation);
const edges = new EdgesGeometry(object.geometry);
const material = new LineBasicMaterial({ color: 0xffffff });
const wireframe = new LineSegments(edges, material);
wireframe.scale.copy(object.scale);
wireframe.rotation.copy(object.rotation);
console.log(wireframe.scale);
scene.add(wireframe);
}
};
But now the boundary's scale and rotation are totally different with the fbxmodel.
And also the boundary is too complex, is it possible to create one only include the outline and the top point.
Thank you. :)

Related

Cannon.js and Three.js – Character Control with Physics

I am trying to build a little world with a Third Person Controller.
For the character, I followed this tutorial by SimonDev and just changed the model from a FBX to GLTF.
Now, I try to implement a Physics World with Cannon.js.
I got it to the point where the collider body is positioned at the starting point of the model. But it stays there after I move the model. I need the collider body to be attached at the model.
I know that I should move the collider body and update the character model to that position but I cannot get it to work. This is my current code. Maybe it is just a simple solution but I am new to Cannon.js, so any help is appreciated. Thank you!
class BasicCharacterController {
constructor(experience, params) {
this.experience = experience;
this._Init(params);
}
_Init(params) {
this._params = params;
this._decceleration = new THREE.Vector3(-0.0005, -0.0001, -5.0);
this._acceleration = new THREE.Vector3(1, 0.25, 50.0);
this._velocity = new THREE.Vector3(0, 0, 0);
this._position = new THREE.Vector3();
this._animations = {};
this._input = new BasicCharacterControllerInput();
this._stateMachine = new CharacterFSM(
new BasicCharacterControllerProxy(this._animations));
this._LoadModels();
}
_LoadModels() {
this.physicsCharacterShape = new CANNON.Box(new CANNON.Vec3(0.5, 1, 0.5));
this.physicsCharacterBody = new CANNON.Body({
mass: 0,
shape: this.physicsCharacterShape,
position: new CANNON.Vec3(0, 0, 0)
});
this.experience.physicsWorld.addBody(this.physicsCharacterBody);
this.gltfLoader = new GLTFLoader();
this.gltfLoader.setPath('./sources/assets/');
this.gltfLoader.load('VSLBINA_TPOSE_GLTF.gltf', (gltf) => {
gltf.scene.traverse(c => {
c.castShadow = true;
});
this._target = gltf.scene;
this._params.scene.add(this._target);
this._target.position.copy(this.physicsCharacterBody.position);
this._target.quaternion.copy(this.physicsCharacterBody.quaternion);
this._mixer = new THREE.AnimationMixer(this._target);
this._manager = new THREE.LoadingManager();
this._manager.onLoad = () => {
this._stateMachine.SetState('idle');
};
const _OnLoad = (animName, anim) => {
const clip = anim.animations[0];
const action = this._mixer.clipAction(clip);
this._animations[animName] = {
clip: clip,
action: action,
};
};
const loader = new GLTFLoader(this._manager);
loader.setPath('./sources/assets/');
loader.load('VSLBINA_WALKING_GLTF.gltf', (a) => { _OnLoad('walk', a); });
loader.load('VSLBINA_IDLE_GLTF.gltf', (a) => { _OnLoad('idle', a); });
});
}
get Position() {
return this._position;
}
get Rotation() {
if (!this._target) {
return new THREE.Quaternion();
}
return this._target.quaternion;
}
Update(timeInSeconds) {
if (!this._stateMachine._currentState) {
return;
}
this._stateMachine.Update(timeInSeconds, this._input);
const velocity = this._velocity;
const frameDecceleration = new THREE.Vector3(
velocity.x * this._decceleration.x,
velocity.y * this._decceleration.y,
velocity.z * this._decceleration.z
);
frameDecceleration.multiplyScalar(timeInSeconds);
frameDecceleration.z = Math.sign(frameDecceleration.z) * Math.min(
Math.abs(frameDecceleration.z), Math.abs(velocity.z));
velocity.add(frameDecceleration);
const controlObject = this._target;
const _Q = new THREE.Quaternion();
const _A = new THREE.Vector3();
const _R = controlObject.quaternion.clone();
const acc = this._acceleration.clone();
if (this._input._keys.shift) {
acc.multiplyScalar(2.0);
}
if (this._input._keys.forward) {
velocity.z += acc.z * timeInSeconds;
}
if (this._input._keys.backward) {
velocity.z -= acc.z * timeInSeconds;
}
if (this._input._keys.left) {
_A.set(0, 1, 0);
_Q.setFromAxisAngle(_A, 4.0 * Math.PI * timeInSeconds * this._acceleration.y);
_R.multiply(_Q);
}
if (this._input._keys.right) {
_A.set(0, 1, 0);
_Q.setFromAxisAngle(_A, 4.0 * -Math.PI * timeInSeconds * this._acceleration.y);
_R.multiply(_Q);
}
controlObject.quaternion.copy(_R);
const oldPosition = new THREE.Vector3();
oldPosition.copy(controlObject.position);
const forward = new THREE.Vector3(0, 0, 1);
forward.applyQuaternion(controlObject.quaternion);
forward.normalize();
const sideways = new THREE.Vector3(1, 0, 0);
sideways.applyQuaternion(controlObject.quaternion);
sideways.normalize();
sideways.multiplyScalar(velocity.x * timeInSeconds);
forward.multiplyScalar(velocity.z * timeInSeconds);
controlObject.position.add(forward);
controlObject.position.add(sideways);
this._position.copy(controlObject.position);
if (this._mixer) {
this._mixer.update(timeInSeconds);
};
// Physics Collider Body
// if (this._target) {
// this._target.position.copy(this.physicsCharacterBody.position);
// this._target.quaternion.copy(this.physicsCharacterBody.quaternion);
// }
}
};

Three.js glb models are showing up black

I am trying to render a glb 3d model using Three.js. I am new to Three.js, and all of my models are coming up black. Here is my code:
import { useState, useEffect, useRef, useCallback } from 'react'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { loadGLTFModel } from '../lib/model'
import { DogSpinner, DogContainer } from './voxel-dog-loader'
function easeOutCirc(x) {
return Math.sqrt(1 - Math.pow(x - 1, 4))
}
const VoxelDog = () => {
const refContainer = useRef()
const [loading, setLoading] = useState(true)
const [renderer, setRenderer] = useState()
const [_camera, setCamera] = useState()
const [target] = useState(new THREE.Vector3(-0.5, 1.2, 0))
const [initialCameraPosition] = useState(
new THREE.Vector3(
20 * Math.sin(0.2 * Math.PI),
10,
20 * Math.cos(0.2 * Math.PI)
)
)
const [scene] = useState(new THREE.Scene())
const [_controls, setControls] = useState()
const handleWindowResize = useCallback(() => {
const { current: container } = refContainer
if (container && renderer) {
const scW = container.clientWidth
const scH = container.clientHeight
renderer.setSize(scW, scH)
}
}, [renderer])
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
const { current: container } = refContainer
if (container && !renderer) {
const scW = container.clientWidth
const scH = container.clientHeight
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(scW, scH)
renderer.outputEncoding = THREE.sRGBEncoding
container.appendChild(renderer.domElement)
setRenderer(renderer)
// 640 -> 240
// 8 -> 6
const scale = scH * 0.005 + 4.8
const camera = new THREE.OrthographicCamera(
-scale,
scale,
scale,
-scale,
0.01,
50000
)
camera.position.copy(initialCameraPosition)
camera.lookAt(target)
setCamera(camera)
const ambientLight = new THREE.AmbientLight(0xcccccc, 1)
scene.add(ambientLight)
const controls = new OrbitControls(camera, renderer.domElement)
controls.autoRotate = true
controls.target = target
setControls(controls)
loadGLTFModel(scene, '/MacbookPro.obj', {
receiveShadow: false,
castShadow: false
}).then(() => {
animate()
setLoading(false)
})
let req = null
let frame = 0
const animate = () => {
req = requestAnimationFrame(animate)
frame = frame <= 100 ? frame + 1 : frame
if (frame <= 100) {
const p = initialCameraPosition
const rotSpeed = -easeOutCirc(frame / 120) * Math.PI * 20
camera.position.y = 10
camera.position.x =
p.x * Math.cos(rotSpeed) + p.z * Math.sin(rotSpeed)
camera.position.z =
p.z * Math.cos(rotSpeed) - p.x * Math.sin(rotSpeed)
camera.lookAt(target)
} else {
controls.update()
}
renderer.render(scene, camera)
}
return () => {
console.log('unmount')
cancelAnimationFrame(req)
renderer.dispose()
}
}
}, [])
useEffect(() => {
window.addEventListener('resize', handleWindowResize, false)
return () => {
window.removeEventListener('resize', handleWindowResize, false)
}
}, [renderer, handleWindowResize])
return (
<DogContainer ref={refContainer}>{loading && <DogSpinner />}</DogContainer>
)
}
export default VoxelDog
I thought it may be an issue with the models themselves, but it has happened with multiple different models so I'm starting to think it may be an issue with how I am rendering the models.
Does anyone have any suggestions? Thank you!

The model scale is too small when using in web application project

I downloaded this model from https://sketchfab.com/3d-models/dcb3a7d5b1ad4f948aa4945d6e378c8a , The model scale is appearing normal when open in Windows 3D Viewer, three.js glTF Viewer and Babylon.js View, but when load the model in three.js module, some model's scale is incorrect, for example.
three.js in Website
three.js glTF Viewer
Babylon.js Viewer
This model scale is correct, when open in another application, it scale correctly.
Model name : dog.glb
Source : https://github.com/craftzdog/craftzdog-homepage
three.js in Website
three.js glTF Viewer
Babylon.js Viewer
This model scale is incorrect and so tiny, but when open in another application, it scale correctly
Model name : แมวประเทศไทย
Source : https://sketchfab.com/3d-models/dcb3a7d5b1ad4f948aa4945d6e378c8a
Here is GLTF Loader Code
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
export function loadGLTFModel(
scene,
glbPath,
options = { receiveShadow: true, castShadow: true }
) {
const { receiveShadow, castShadow } = options
return new Promise((resolve, reject) => {
const loader = new GLTFLoader()
loader.load(
glbPath,
gltf => {
const obj = gltf.scene
obj.name = 'persian'
obj.position.x = 0
obj.position.y = 0
obj.receiveShadow = receiveShadow
obj.castShadow = castShadow
scene.add(obj)
obj.traverse(function (child) {
if (child.isMesh) {
child.castShadow = castShadow
child.receiveShadow = receiveShadow
}
})
resolve(obj)
},
undefined,
function (error) {
reject(error)
}
)
})
}
Here is Model Display Code
import { useState, useEffect, useRef, useCallback } from 'react'
import { Box, Spinner } from '#chakra-ui/react'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { loadGLTFModel } from '../lib/model'
function easeOutCirc(x) {
return Math.sqrt(1 - Math.pow(x - 1, 4))
}
const PersianCat = () => {
const refContainer = useRef()
const [loading, setLoading] = useState(true)
const [renderer, setRenderer] = useState()
const [_camera, setCamera] = useState()
const [target] = useState(new THREE.Vector3(-0.5, 1.2, 0))
const [initialCameraPosition] = useState(
new THREE.Vector3(
20 * Math.sin(0.2 * Math.PI),
10,
20 * Math.cos(0.2 * Math.PI)
)
)
const [scene] = useState(new THREE.Scene())
const [_controls, setControls] = useState()
// On component mount only one time.
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
const { current: container } = refContainer
if (container && !renderer) {
const scW = container.clientWidth
const scH = container.clientHeight
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(scW, scH)
renderer.outputEncoding = THREE.sRGBEncoding
container.appendChild(renderer.domElement)
setRenderer(renderer)
// 640 -> 240
// 8 -> 6
const scale = scH * 0.005 + 4.8
const camera = new THREE.OrthographicCamera(
-scale,
scale,
scale,
-scale,
0.01,
50000
)
camera.position.copy(initialCameraPosition)
camera.lookAt(target)
setCamera(camera)
const ambientLight = new THREE.AmbientLight(0xcccccc, 1)
scene.add(ambientLight)
const controls = new OrbitControls(camera, renderer.domElement)
controls.autoRotate = true
controls.target = target
setControls(controls)
loadGLTFModel(scene, '/persian.glb', {
receiveShadow: false,
castShadow: false
}).then(() => {
animate()
setLoading(false)
})
let req = null
let frame = 0
const animate = () => {
req = requestAnimationFrame(animate)
frame = frame <= 100 ? frame + 1 : frame
if (frame <= 100) {
const p = initialCameraPosition
const rotSpeed = -easeOutCirc(frame / 120) * Math.PI * 20
camera.position.y = 10
camera.position.x =
p.x * Math.cos(rotSpeed) + p.z * Math.sin(rotSpeed)
camera.position.z =
p.z * Math.cos(rotSpeed) - p.x * Math.sin(rotSpeed)
camera.lookAt(target)
} else {
controls.update()
}
renderer.render(scene, camera)
}
return () => {
cancelAnimationFrame(req)
renderer.dispose()
}
}
}, [])
return (
<Box
ref={refContainer}
className="persian-cat"
m="auto"
at={['-20px', '-60px', '-120px']}
mb={['-40px', '-140px', '-200px']}
w={[280, 480, 640]}
h={[280, 480, 640]}
position="relative"
>
{loading && (
<Spinner
size="xl"
position="absolute"
left="50%"
top="50%"
ml="calc(0px - var(--spinner-size) / 2)"
mt="calc(0px - var(--spinner-size))"
/>
)}
</Box>
)
}
export default PersianCat

Drag&Drop element into canvas from HTML

I've got a very trivial task to drag&drop the element from a gallery into the Three.js canvas.
Nothing seems tricky until I face the problem that when I drag the item and add it to the scene I cannot update the item's coordinates until I finish the drag&drop event
I already played around with all events that watch the mouse event mousemove, drag, dragover but the element is just stuck at the initial coordinates, the ones I applied in the dragenter event
export const params = {
devicePixelRatio: Math.min(window.devicePixelRatio, 2),
size: getSizeParams(),
grid: {
size: 20,
divisions: 20,
},
}
const itemProtos = ['Box', 'Sphere', 'Cone']
export const canvas = document.querySelector(`#canvas`)
const raycaster = new Raycaster()
const pointer = new Vector2()
const scene = new Scene()
const camera = new PerspectiveCamera(75, params.size.width / params.size.height, 0.1, 100)
camera.position.z = 5
camera.position.y = 2
/**
* Variable for Drag&Drop - just created object that's being moved around
*/
let newObjectType = null
let newObject = null
/**
* Groups
*/
export const itemGroup = new Group()
scene.add(itemGroup)
/**
* Grid
*/
export const gridHelper = new GridHelper(params.grid.size, params.grid.divisions)
scene.add(gridHelper)
/**
* Renderer
*/
const renderer = new WebGLRenderer({
canvas,
antialias: true,
})
renderer.setSize(params.size.width, params.size.height)
renderer.setPixelRatio(params.devicePixelRatio)
/**
* Resizing updates to fit the screen
*/
window.addEventListener('resize', () => {
params.size = getSizeParams()
camera.aspect = params.size.width / params.size.height
camera.updateProjectionMatrix()
renderer.setSize(params.size.width, params.size.height)
renderer.setPixelRatio()
})
canvas.addEventListener('mouseenter', () => {
canvas.style.cursor = 'grab'
})
/**
* Controls
*/
const orbitControls = new OrbitControls(camera, canvas)
orbitControls.enableDamping = true
orbitControls.addEventListener('start', () => {
canvas.style.cursor = 'grabbing'
})
orbitControls.addEventListener('end', () => {
canvas.style.cursor = 'grab'
})
const tick = () => {
orbitControls.update()
requestAnimationFrame(tick)
renderer.render(scene, camera)
}
window.onload = tick
/**
* Raycaster functions
*/
const refreshMouseCoords = (event) => {
pointer.x = (event.clientX / params.size.width) * 2 - 1
pointer.y = -(event.clientY / params.size.height) * 2 + 1
}
let currentIntersect = null
let currentPick = null
canvas.addEventListener('mousemove', (event) => {
refreshMouseCoords(event)
raycaster.setFromCamera(pointer, camera)
const intersects = raycaster.intersectObjects(itemGroup.children, false)
if(intersects.length && intersects[0].object instanceof Mesh) {
if(!currentIntersect) {
canvas.style.cursor = 'all-scroll'
intersects[0].object.material.color.set('red')
console.log(`mouse enter`)
}
if(currentIntersect && currentIntersect !== intersects[0].object) {
currentIntersect.material.color.set('blue')
intersects[0].object.material.color.set('red')
}
currentIntersect = intersects[0].object
} else {
if(currentIntersect) {
console.log(`mouse leave`)
currentIntersect.material.color.set('blue')
canvas.style.cursor = 'grab'
}
currentIntersect = null
}
moveItem(currentPick)
})
/**
* Function to move items around GridHelper
*/
const moveItem = (item) => {
const intersectsGround = raycaster.intersectObject(gridHelper, false)
if(item && intersectsGround[0]) {
item.position.z = intersectsGround[0].point.z
item.position.x = intersectsGround[0].point.x
}
}
canvas.addEventListener('mousedown', (event) => {
event.preventDefault()
refreshMouseCoords(event)
raycaster.setFromCamera(pointer, camera)
const intersects = raycaster.intersectObjects(itemGroup.children, false)
if(intersects.length && intersects[0].object instanceof Mesh) {
currentPick = intersects[0].object
}
if(currentIntersect) {
canvas.style.cursor = 'all-scroll'
orbitControls.enabled = false
}
})
canvas.addEventListener('mouseup', () => {
if(currentIntersect) {
canvas.style.cursor = 'all-scroll'
orbitControls.enabled = true
}
if(currentPick) {
currentPick = null
}
})
// *** Drag&Drop *** //
const gallery = document.querySelector(`#gallery`)
setGallery(itemProtos, gallery)
canvas.addEventListener('dragenter', (event) => {
event.preventDefault()
console.log('Drag&Drop: dragenter')
refreshMouseCoords(event)
raycaster.setFromCamera(pointer, camera)
const intersects = raycaster.intersectObject(gridHelper)
if (intersects.length && newObjectType) {
add3DEl(intersects[0].point, newObjectType, itemGroup)
}
})
canvas.addEventListener('dragover', (event) => {
event.preventDefault()
event.stopPropagation()
if(newObject) {
moveItem(newObject)
}
})
function setGallery(itemProtos, gallery) {
for (let i of itemProtos) {
const el = createProto()
gallery.appendChild(el)
el.addEventListener('dragstart', function (event) {
event.dataTransfer.setData('text/plain', i)
newObjectType = i
})
el.addEventListener('dragend', function () {
newObjectType = null
newObject = null
})
}
}
function add3DEl({ x, z }, type = 'Box', scene) {
const geometry = new itemObjects[`${type}Geometry`]()
const material = new MeshBasicMaterial({ color: 0x0000ff }) // Shared material for all items
material.wireframe = true
const el = new Mesh(geometry, material)
el.position.x = x
el.position.y = type == 'Sphere' ? 1 : .5
el.position.z = z
el.userData.name = `${type}_${Date.now()}`
newObject = el
scene.add(el)
}
Here you can find a playgroud with what I've got so far: Playground

Error when importing a .gltf file into threejs (in expo)

When importing a .gltf file into threejs (in expo), I get the following error:
Error: Event {
"isTrusted": false,
}
I am using the GLTFLoader from three(/examples/jsm/loaders/GLTFLoader.js) in an expo react native project with the following code:
loader.load(
"../body.gltf",
(gltf) => {
console.log("Object: ", gltf);
scene.add(gltf.scene);
},
(progress) => console.log("Progress: ", progress),
(err) => console.log("Error: ", err)
);
Is there something in my permissions I am not aware of, is it something in expo or something else?
Loading assets:
useEffect(() => {
(async () => {
const assets = [
Asset.fromModule(require("./assets/abductors.gltf")),
Asset.fromModule(require("./assets/abs.gltf")),
Asset.fromModule(require("./assets/adductors.gltf")),
Asset.fromModule(require("./assets/biceps.gltf")),
Asset.fromModule(require("./assets/bracheoradialis.gltf")),
Asset.fromModule(require("./assets/calves.gltf")),
Asset.fromModule(require("./assets/chest.gltf")),
Asset.fromModule(require("./assets/feet.gltf")),
Asset.fromModule(require("./assets/flexors.gltf")),
Asset.fromModule(require("./assets/forearms.gltf")),
Asset.fromModule(require("./assets/glutes.gltf")),
Asset.fromModule(require("./assets/hamstrings.gltf")),
Asset.fromModule(require("./assets/hands.gltf")),
Asset.fromModule(require("./assets/head.gltf")),
Asset.fromModule(require("./assets/lats.gltf")),
Asset.fromModule(require("./assets/obliques.gltf")),
Asset.fromModule(require("./assets/pelvic.gltf")),
Asset.fromModule(require("./assets/quads.gltf")),
Asset.fromModule(require("./assets/rotators.gltf")),
Asset.fromModule(require("./assets/serratus.gltf")),
Asset.fromModule(require("./assets/shoulders.gltf")),
Asset.fromModule(require("./assets/tibalis.gltf")),
Asset.fromModule(require("./assets/transverse.gltf")),
Asset.fromModule(require("./assets/traps.gltf")),
Asset.fromModule(require("./assets/triceps.gltf"))
];
let body = new THREE.Scene();
for (let i = 0; i < assets.length; i++) {
await assets[i].downloadAsync();
const loader = new GLTFLoader();
loader.load(
assets[i].uri || "",
(gltf) => {
assets[i] = gltf.scene;
body.add(gltf.scene);
},
(xhr) => {
console.log(`${(xhr.loaded / xhr.total) * 100}% loaded`);
},
(error) => {
console.error("An error happened", error);
}
);
}
setBody(body);
})();
}, []);
Showing them:
const _onContextCreate = async (gl) => {
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
let renderer = new ExpoTHREE.Renderer({ gl });
renderer.setSize(width, height);
renderer.setClearColor(0xffffff);
let camera = new THREE.PerspectiveCamera(80, width / height, 0.01, 1000);
camera.position.z = 5;
let scene = new THREE.Scene();
const light = new THREE.PointLight(0xffffff, 1, 500);
light.position.set(5, 10, 10);
scene.add(light);
scene.add(body);
body.position.set(0, -2, -5);
body.scale.set(1.2, 1.2, 1.2);
body.rotation.set(0, 0, 0);
[...]
};
return (
<Model
_onContextCreate={_onContextCreate}
body={body}
setActiveMuscles={setActiveMuscles}
onChangeStroke={onChangeStroke}
/>
);

Resources