Three.js鼠标拖动设置骨骼姿态

在这里插入图片描述
在这里插入图片描述

实现

  • 根据SkinnedMesh生成Mesh 作为射线检测的目标(射线检测SkinnedMesh存在不足 无法应用骨骼形变的顶点 )
  • 点击模型 获取点击位置对应的骨骼
  • 拖拽鼠标设置骨骼旋转角度(使用TransformControl选中点击的骨骼 设置轴为XYZE 并隐藏控件 主动触发控件对应的方法 模拟拖拽控件的过程 )
  • 每次变更后更新生成的Mesh

细节

  • 更改TransformControls源码 屏蔽原本行为
  • 生成的Mesh需要使用新的类 这个类继承THREE.Mesh 覆盖其raycast方法 优化射线检测需要更新boundingBox和boundingSphere所需的开销 跳过boundingShere检测过程

源码

  • gitee
    运行项目后访问地址为http://localhost:3000/transform

核心代码

import * as THREE from "three";
import { ThreeHelper } from "@/src/ThreeHelper";
import { MethodBaseSceneSet, LoadGLTF } from "@/src/ThreeHelper/decorators";
import { MainScreen } from "./Canvas";
import type { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
import { CCDIKSolver, CCDIKHelper } from "@/src/ThreeHelper/addons/CCDIKSolver";
import EventMesh from "@/src/ThreeHelper/decorators/EventMesh";
import type { BackIntersection } from "@/src/ThreeHelper/decorators/EventMesh";
import { Injectable } from "@/src/ThreeHelper/decorators/DI";
// import { TransformControls } from "three/examples/jsm/controls/TransformControls";
import { TransformControls } from "@/src/ThreeHelper/addons/TransformControls";@Injectable
export class Main extends MainScreen {static instance: Main;bones: THREE.Bone[] = [];boneIndex: number = -1;transformControls!: TransformControls;constructor(private helper: ThreeHelper) {super(helper);this.init();Main.instance = this;}@MethodBaseSceneSet({addAxis: false,cameraPosition: new THREE.Vector3(0, 1, 3),cameraTarget: new THREE.Vector3(0, 1, 0),near: 0.1,far: 100,})init() {this.initGui();this.loadModel();const transformControls = new TransformControls(this.helper.camera,this.helper.renderer.domElement);this.transformControls = transformControls;transformControls.mode = "rotate";this.helper.scene.add(transformControls);transformControls.addEventListener("mouseDown",() => (this.helper.controls.enabled = false));transformControls.addEventListener("mouseUp", () => {const { object } = transformControls;if (object && object.userData) {object.userData.updateTemplatePosition &&object.userData.updateTemplatePosition();}this.helper.controls.enabled = true;});// translate// transformControls.ExplicitTrigger('XYZ');// rotatetransformControls.ExplicitTrigger("XYZE");// this.helper.useSkyEnvironment();this.helper.setBackgroundDHR("/public/env/sunflowers_puresky_2k/");}@LoadGLTF("/public/models/michelle.glb")// @LoadGLTF("/public/models/observer1.gltf")// @LoadGLTF("/public/models/box.glb")loadModel(gltf?: GLTF) {if (gltf) {this.helper.add(gltf.scene);this.initBoneGui(gltf);const skeletonAnimation = new this.helper.SkeletonAnimation();skeletonAnimation.init(gltf.scene, gltf.animations);this.helper.gui?.addFunction(() => {skeletonAnimation.update();}, "播放骨骼动画");// requestAnimationFrame(() => {//     this.createMeshTemplate();// });}}@EventMesh.OnMouseDown(Main)handleMouseDown(backIntersection: BackIntersection,info: BackIntersection,event: MouseEvent) {if (backIntersection &&backIntersection.faceIndex &&backIntersection.object?.geometry?.index) {console.log(backIntersection.faceIndex);//* 通过 faceIndex 找到 geometry.index[index]  再找到 attributes.skinIndex[index] 就是骨骼的索引const skinIndex = backIntersection.object.geometry.index.getX(backIntersection.faceIndex * 3);const boneIndex =backIntersection.object.geometry.attributes.skinIndex.getX(skinIndex);const bone = this.bones[boneIndex];console.log(bone);if (!bone) return;window.bone = bone;this.transformControls.attach(bone);this.transformControls.pointerDown(this.transformControls._getPointer(event));EventMesh.enabledMouseMove = true;// if (bone) {//     this.bone = bone;//     this.boneIndex = boneIndex;//     this.generateIKStruct(//         <THREE.SkinnedMesh>(<unknown>backIntersection.object),//         bone,//         boneIndex//     );//     this.helper.controls.enabled = false;//     EventMesh.enabledMouseMove = true;// }}}@EventMesh.OnMouseMove(Main)handleMouseMove(event: MouseEvent) {this.transformControls.pointerMove(this.transformControls._getPointer(event));}@EventMesh.OnMouseUp(Main)handleMouseUpControl(event: MouseEvent) {this.transformControls.pointerUp(this.transformControls._getPointer(event));if (EventMesh.enabledMouseMove) {EventMesh.enabledMouseMove = false;}}initBoneGui(gltf: GLTF) {const skeleton = new THREE.SkeletonHelper(gltf!.scene);EventMesh.setIntersectObjects([gltf!.scene]);this.bones = skeleton.bones;const control = {addHelper:() => {this.helper.add(skeleton);this.helper.gui?.add(<any>skeleton, "visible").name("显示骨骼");}}this.helper.gui?.addFunction(control.addHelper).name("蒙皮骨骼");console.log(this.bones);}initGui() {const gui = this.helper.gui!;if (!gui) this.helper.addGUI();return gui;}@ThreeHelper.InjectAnimation(Main)animation() {}createMeshTemplate() {this.helper.scene.traverse((obj) => {if (obj.type == "SkinnedMesh") {const cloneMesh = this.helper.SkinnedToMesh(<THREE.SkinnedMesh>obj);cloneMesh.visible = false// this.helper.add(cloneMesh);obj.parent?.add(cloneMesh);console.log(cloneMesh);window.cloneMesh = cloneMesh;// cloneMesh.position.x += 50;EventMesh.setIntersectObjects([cloneMesh]);// const {center,radius} = cloneMesh.geometry.boundingSphere// const {center,radius} = obj.geometry.boundingSphere// const {mesh} = this.helper.create.sphere(radius,32,32)// mesh.position.copy(center)// this.helper.add(mesh)}});}
}

覆盖Mesh的raycast方法

/** @Author: hongbin* @Date: 2024-06-27 15:32:48* @LastEditors: hongbin* @LastEditTime: 2024-06-27 16:00:22* @Description:*/
import * as THREE from "three";const _ray = new THREE.Ray();
const _inverseMatrix = new THREE.Matrix4();export class RayMesh extends THREE.Mesh {/*** 拦截Mesh的raycast方法 不检测 boundingSphere 配合SkinnedMesh转换的Mesh的射线使用*/constructor(...arg: any[]) {super(...arg);}raycast(raycaster: THREE.Raycaster, intersects: any) {const geometry = this.geometry;const material = this.material;const matrixWorld = this.matrixWorld;if (material === undefined) return;_ray.copy(raycaster.ray).recast(raycaster.near);_inverseMatrix.copy(matrixWorld).invert();_ray.copy(raycaster.ray).applyMatrix4(_inverseMatrix);// test with bounding box in local spaceif (geometry.boundingBox !== null) {if (_ray.intersectsBox(geometry.boundingBox) === false) return;}// test for intersections with geometrysuper._computeIntersections(raycaster, intersects, _ray);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/36660.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PostgreSQL计算 queryid 原理

数据库版本 PG 16.1 queryid 是什么 queryid 是将 sql 规范化 (normalization) 后&#xff0c;通过哈希函数计算出来的 64 位整数。 以 SELECT id, data FROM tbl_a WHERE id < 300 ORDER BY data; 这条 SQL 为例。当我们在 PG 中执行这条 sql 时&#xff0c;内核在语义…

【STM32-DAP 仿真器】

STM32-DAP 仿真器 ■ STM32-DAP仿真器介绍■ STM32-DAP仿真特点■ STM32-DAP仿真器实物图■ STM32-DAP高速 DAP 仿真器实物图■ STM32-DAP高速无线调试器 实物图■ STM32-DAP高速无线调试器示意图■ STM32-DAP高速无线调试器接线图■ STM32-DAP高速无线调试器接收端示意图 ■ S…

vcruntime140_1.dll是什么东东?vcruntime140_1.dll缺失的8个解决方法

当电脑出现找不到vcruntime140_1.dll,或vcruntime140_1.dll丢失无法打开软件怎么办&#xff1f;小编今天在本文详细为大家介绍解决方法与介绍vcruntime140_1.dll究竟是什么等vcruntime140_1.dll的问题。 一、vcruntime140_1.dll文件是什么 文件概述定义与功能 vcruntime140_…

CAN收发器

1、收发器的主要功能 &#xff08;1&#xff09;CAN通讯&#xff08;即报文收发&#xff09; MCU要CAN通讯&#xff1a;收发器模式切换至正常通讯模式&#xff08;Normal&#xff09;&#xff0c;正常通讯模式收发器能收能发。 MCU不要CAN通讯&#xff1a;把收发器切换至其它…

format()函数

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法介绍 format()可以对数据进行格式化处理操作&#xff0c;语法如下&#xff1a; format(value, format_spec) format_spec为格式化解释。当参数…

C语言笔记26 •顺序表应用•

基于动态顺序表实现通讯录项目 1.通讯录其实也就是顺序表&#xff0c;就是把里面存的数据类型变了一下 &#xff0c;所以有一些方法对于顺序表适用&#xff0c;对于通讯录也是适用的&#xff08;初始化&#xff0c;销毁&#xff0c;内存空间扩容&#xff09;。 2.要用到顺序表…

【设计模式】行为型-策略模式

策略模式&#xff0c;如春风吹过&#xff0c;随心所欲&#xff0c;变幻无穷&#xff0c;每一丝风都是一种选择。 文章目录 一、订单处理二、策略模式三、策略模式的核心组成四、运用策略模式五、策略模式的应用场景六、小结推荐阅读 一、订单处理 场景假设&#xff1a;有一个…

MySQL高级-索引-设计原则小结

文章目录 1、设计原则2、索引小结2.1、索引概述2.2、索引结构2.3、索引分类2.4、索引语法2.5、SQL性能分析2.6、索引使用2.7、索引设计原则 1、设计原则 针对于数据量较大&#xff0c;且查询比较频繁的表建立索引。针对于常作为查询条件&#xff08;where&#xff09;、排序&am…

2毛钱的SOT23-5封装28V、1.5A、1.2MHz DCDC转换器用于LCD偏置电源和白光LED驱动等MT3540升压芯片

前言 之前发了一个TI的BOOST升压芯片&#xff0c;用于LCD偏置电压或LED驱动&#xff0c;请访问以下链接。 6毛钱SOT-23封装28V、400mA 开关升压转换器&#xff0c;LCD偏置电源和白光LED应用芯片TPS61040 国产半导体厂家发展迅猛&#xff0c;今天推荐一个公司带“航天”的升压…

Java基础知识整理笔记

目录 1.关于Java概念 1.1 谈谈对Java的理解&#xff1f; 1.2 Java的基础数据类型&#xff1f; 1.3 关于面向对象的设计理解 1.3.1 面向对象的特性有哪些&#xff1f; 1.3.2 重写和重载的区别&#xff1f; 1.3.3 面向对象的设计原则是什么&#xff1f; 1.4 关于变量与方…

搭建 MySQL MHA

搭建 MySQL MHA 搭建 MySQL MHA实验拓扑图实验环境实验思路MHA架构故障模拟 实验部署数据库安装主从复制部署时间同步主服务器配置从服务器配置创建链接 MHA搭建安装依赖的环境安装 node 组件安装 manager 组件配置无密码认证在 manager 节点上配置 MHA管理 mysql 节点服务器创…

面试突击:Java 集合知识体系梳理

本文已收录于&#xff1a;https://github.com/danmuking/all-in-one&#xff08;持续更新&#xff09; 前言 哈喽&#xff0c;大家好&#xff0c;我是 DanMu。在 Java 开发中&#xff0c;集合类对象绝对是被使用最频繁的对象之一。因此&#xff0c;深入了解集合类对象的底层数…

热敏晶振:成本效益的选择与温补晶振的比较

在精密电子系统的设计中&#xff0c;晶振作为时间基准源&#xff0c;其频率稳定性直接影响到整个系统的性能。其中&#xff0c;温补晶振(Temperature Compensated Crystal Oscillator&#xff0c;简称TCXO)与热敏晶振(Thermistor Compensated Crystal Oscillator)作为在特殊温度…

面试-java异常体系

1.java异常体系 error类是指与jvm相关的问题。如系统崩溃&#xff0c;虚拟机错误&#xff0c;内存空间不足。 非runtime异常不处理&#xff0c;程序就没有办法执行。 一旦遇到异常抛出&#xff0c;后面的异常就不会进行。 (1)常见的error以及exception 2.java异常要点分析…

kubekey 安装高可用 kubernetes 集群

1. 准备环境 1.1 机器准备 4 台机器&#xff0c;操作系统&#xff1a;Ubuntu 24.04/RHEL8/CentOS9 10.111.3.53 master1 10.111.3.54 master2 10.111.3.55 master3 10.111.3.57 node41.2 安装依赖和配置 所有节点都需要执行&#xff1a; Ubuntu: apt-get install -y soca…

【AI大模型】Transformers大模型库(十四):Datasets Viewer

目录 一、引言 二、Datasets Viewer数据查看器 2.1 概述 2.2 示例 三、总结 一、引言 这里的Transformers指的是huggingface开发的大模型库&#xff0c;为huggingface上数以万计的预训练大模型提供预测、训练等服务。 &#x1f917; Transformers 提供了数以千计的预训练…

如何使用大模型进行文本分类任务?

暑期实习基本结束了&#xff0c;校招即将开启。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友解惑答疑&…

openlayer 鼠标点击船舶,打开船舶简单弹框

背景&#xff1a; 对创建的地图对象&#xff0c;可以添加上监听事件&#xff0c;常用的有&#xff1a;地图点击事件、鼠标移动事件。 通过监听这些事件&#xff0c;又可以区分不同图层的不同要素&#xff0c;获取不同数据&#xff1b; 根据这些数据&#xff0c;又可以发起网络请…

【介绍下SCSS的基本使用】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

React的Props、生命周期

Props 的只读性 “Props” 是 React 中用于传递数据给组件的一种机制&#xff0c;通常作为组件的参数进行传递。在 React 中&#xff0c;props 是只读的&#xff0c;意味着一旦将数据传递给组件的 props&#xff0c;组件就不能直接修改这些 props 的值。所以组件无论是使用函数…