babylon.js魔方建模

news2024/9/24 15:18:51

本文主要内容可能和babylon并无太紧密的关联, 主要是对旋转( 空间想象力 )的练习。 本来想写个魔方练练,就想着顺便练练baboly. 结果反正是最重要的交互逻辑没有实现。

标题已经说明了本文的主题是建模,也就是说,是可以用建模软件来替代这部分的代码实现。不过,从性能上说,肯定是直接代码顶点实现要好些。

关于拧转魔方

之前想到一个思路,就是每次触发转动时其实,就要知道转动的那一层,把八个块都添加到中间的chidren里,这样就可以一起转动了。缺点就是要能判断开始和结束,每次转动开始就整体化,结束就把八个块再移回去,似乎,还不如直接用改变旋转中心的办法。

开始建模

魔方的组成

我是要做一个魔方,因此,常规的立方体并不适用,因为需要每个面都有独立的颜色,也许用立方体纹理可以解决,但是合成这个天空盒就很麻烦。 直接用多个平面拼凑吧, 这样最少可以减少一半的面。

实际上 ,魔方的每一小块最多就只会显示三个面,其余的面看不到,就可以直接认为没有,那我们就可以偷工减料。 现实的魔方也是这样的,不过,这里可以比它更省

一个魔方小块,实际上就只有三种,拆过魔方的都知道。 没拆过的,现在你也会知道了。

代码接之前的初次使用babylon.js

八个顶角

先来做顶角的类。我这里直接用普通函数,因为不知道babylonjs的分组是怎么做的,我直接用Mesh这个类。

默认是 顶层的 左上角。 那么我们就需要三个面, 上面, 左面和后面。这个函数应该让外部传入每个面的颜色。 至于最后一个参数,是否是底层。是因为遇到麻烦了。

左手系的问题是在这个过程中发现的。 这里,假装我已经知道是左手系,那么想要换成右手系,就直接z取反即可。 用plane通过旋转变换以及平移变换得到一个角,其他的七个角都可以通过一定的旋转变换得到。 我之前确实是这么想的。但是,实际做起来有点问题。

/**
 * 八个角作为角来说只需要三个面 这里以上面的左上角为默认
 */
let id=0 ;
function createCornerBox(colorUp:Color3 , colorLeft:Color3, colorBack:Color3,down = 0 ){ let group= new Mesh('p'+id);let mat= new StandardMaterial('sd'+id) ;let upMat = mat, leftMat = mat.clone('left'+id), backMat = mat.clone('back'+id) ;upMat.diffuseColor= colorUp ;leftMat.diffuseColor = colorLeft ;backMat.diffuseColor = colorBack;const upFace = MeshBuilder.CreatePlane('upF'+id) ;upFace.material = upMat ;upFace.rotation.x = PI*(.5 + down);// babylon的旋转方向是反着来的?upFace.position.y=.5+down ;const leftFace =MeshBuilder.CreatePlane('leftF'+id, {size:1}) ;leftFace.rotation.y = PI/2 ;leftFace.material = leftMat ;leftFace.position.x = -.5 ;const backFace =MeshBuilder.CreatePlane('backF'+ id, {size:1}) ;backFace.rotation.x = PI ;backFace.position.z = .5 ;backFace.material = backMat ;group.addChild(upFace) ;group.addChild(leftFace) ;group.addChild(backFace) ;id++return group ;
} 

第一个角是左上角,只要把这个角,绕Y轴 顺时针旋转90度就可以得到右上角,后面的右下角,左下角以此类推,同时注意颜色。 我这个截图之所以这个角看上去是因为只使用了单面,只能从这个角度观察。

 const corner1 = createCornerBox(upColor,leftColor,backColor) ;// 左上

corner1.position.set(-1,1,1);// z取反了 

顶层, 其余三个角

 const corner2 = createCornerBox(upColor,backColor,rightColor) ; // 右上

const corner3 = createCornerBox(upColor,rightColor,frontColor) ;// 右下
const corner4 = createCornerBox(upColor,frontColor,leftColor) ;// 左下

 // translate是方向和距离
corner2.rotation.y= PI/2 ;
corner2.position.set( 1,1,1) ;

corner3.position.set(1,1,-1) ;
corner4.position.set(-1,1,-1) ;

corner3.rotation.y= PI ;
corner4.rotation.y= -PI/2 ; 

底层的角,按之前的想法就有点麻烦了,比如说,底层的左下角要经过什么样的旋转才能得到,要先绕z旋转180,再绕y顺时针旋转90 , 但是颜色还得换,我觉得麻烦。

于是又加了区分上层和底层, 直接在函数里把底层的底面做好和顶层的上面区分。 这样,底层的代码就很容易写了, 只要全体y值取负,把上面的颜色换成下面的颜色即可。

 upFace.material = upMat ;
 upFace.rotation.x = PI*(.5 + down);// babylon的旋转方向是反着来的?
 upFace.position.y=.5+down ; 

底层八个角。

 // 底面

const corner5 = createCornerBox(downColor,leftColor,backColor, -1) ;// 左上
const corner6 = createCornerBox(downColor,backColor,rightColor, -1) ; // 右上

const corner7 = createCornerBox(downColor,rightColor,frontColor, -1) ;// 右下
const corner8 = createCornerBox(downColor,frontColor,leftColor, -1) ;// 左下

corner5.position.set(-1,-1,1); // translate是方向和距离
corner6.rotation.y= PI/2 ;
corner6.position.set( 1,-1,1) ;

corner7.position.set(1,-1,-1) ;
corner8.position.set(-1,-1,-1) ;

corner7.rotation.y= PI ;
corner8.rotation.y= -PI/2 ; 

这样八个顶角全部完工。

小问题

中途还遇到了一个小问题,但是查了半天。 结果发现是没有初始化engine 就开始建模引起的错误。这里就要再提一提,babylon设计的神奇之处, 实例化的mesh不需要手动添加到scene 中就可以正常渲染,但是scene 这些东西大概也因此必须提前初始化,他们之间有强关联。

轴承

另外,它这个坐标轴辅助的渲染顺序是靠后的,我又不知道如何调整。 所以不用它了,顺便加个轴承。

 /**
 * 轴承 不要那个坐标指示器了
 */
const mat= new StandardMaterial('axisUp') ;
mat.diffuseColor = upColor ;

const upAxis = MeshBuilder.CreateCylinder('up', {height:.5,diameterBottom:.3,diameterTop:.3}) ;
upAxis.position.set(0, .5, 0) ;upAxis.material = mat ;

const downAxis = upAxis.clone('downAxis') ;
downAxis.position.set(0,-.5, 0) ; downAxis.material = mat.clone('down');
(downAxis.material asStandardMaterial).diffuseColor = downColor ;

const leftAxis = upAxis.clone('leftAxis') ;
leftAxis.position.set(-.5,0, 0) ; leftAxis.material = mat.clone('left');
leftAxis.rotation.z = PI/2 ;
(leftAxis.material asStandardMaterial).diffuseColor = leftColor ;

const rightAxis = upAxis.clone('rightAxis') ;
rightAxis.position.set( .5,0, 0) ; rightAxis.material = mat.clone('right');
rightAxis.rotation.z = -PI/2 ;
(rightAxis.material asStandardMaterial).diffuseColor = rightColor ;

const frontAxis = upAxis.clone('frontAxis') ;
frontAxis.position.set( 0, 0,-.5) ; frontAxis.material = mat.clone('front');
frontAxis.rotation.x = PI/2 ;
(frontAxis.material asStandardMaterial).diffuseColor = frontColor ;

const backAxis = upAxis.clone('frontAxis') ;
backAxis.position.set( 0, 0,.5) ; backAxis.material = mat.clone('back');
backAxis.rotation.x = -PI/2 ;
(backAxis.material asStandardMaterial).diffuseColor = backColor ; 

棱边

一个棱边由两个面组成,总共12个。 就不像上面那么啰嗦了,经历了上面的一系列旋转操作,想必你对于欧拉旋转已经了然于胸。

还是先来一个函数工厂, 默认是上层的前方。其余的都通过旋转得到

function createArrisBox(colorFront:Color3, colorUp:Color3){ let group= new Mesh('e'+id);let mat= new StandardMaterial('sde'+id) ;letfrontMat = mat.clone('fronte'+id) ;mat.diffuseColor= colorUp ;frontMat.diffuseColor = colorFront;const upFace = MeshBuilder.CreatePlane('upFe'+id) ;const frontFace = upFace.clone('frontFe'+id) ; upFace.rotation.x = PI/2 ;upFace.position.y = .5 ;upFace.material = mat ;frontFace.position.z = -.5 ;frontFace.material = frontMat ;group.addChild(frontFace);group.addChild(upFace);id++group.scaling.set(.9,.9,.9) // 留间隙return group ;
} 

然后,还是分上中下三层。

 // 顶层
let arris1 = createArrisBox(frontColor, upColor) ; // 前
arris1.position.set(0,1,-1);

let arris2 = createArrisBox(backColor, upColor) ; // 后
arris2.position.set(0,1,1);
arris2.rotation.y =PI ;

let arris3 = createArrisBox(leftColor, upColor) ; // 左
arris3.position.set(-1,1, 0);
arris3.rotation.y =PI/2 ;

let arris4 = createArrisBox(rightColor, upColor) ; // 右
arris4.position.set(1,1,0);
arris4.rotation.y = -PI/2 ;

// 中层 到了发挥想象力的时候,这次我决定全部用 旋转 不额外建模了

let arris5 =createArrisBox( frontColor,leftColor) ; //正面的左棱
arris5.rotation.z = PI/2 ; // 这里是因为这个 前面的法向实际上是-z。所以这里旋转是顺时针90
arris5.position.set(-1,0,-1) ;

let arris6 =createArrisBox( frontColor,rightColor) ; //正面的右棱
arris6.rotation.z = -PI/2 ; // 左右对称 
arris6.position.set(1,0,-1) ;

let arris7 =createArrisBox( rightColor,backColor) ; //背面的右棱 理论上只要在正面的基础上 x轴旋转90 逆时针但是欧拉顺序不是这样的 ,当然可以改变其Order
// arris7.rotation.reorderInPlace('YZX') ; //这个方法不是设置Order的
arris7.rotation.set( 0,-PI/2, -PI/2) ; 
arris7.position.set(1,0,1) ;

let arris8 =createArrisBox( leftColor,backColor) ; //背面的左棱 
// 这次不改其Order 那么就是先顺时针 y再逆时针 -z左手系 我又把坐标改成右手系的,导致z轴方向一直反着,反正看结果就知道了
arris8.rotation.set(0,PI/2, PI/2 ) ; 
arris8.position.set(-1,0,1) ;

// 底层理论上 只要把初始位置绕z轴旋转 180然后绕y轴 旋转一定度数,但是y轴在前,注意

let arris9 =createArrisBox( frontColor, downColor) ; //前 

arris9.rotation.set(0,0, PI ) ; 
arris9.position.set(0,-1,-1) ;

let arris10 =createArrisBox( backColor, downColor) ; //后 

arris10.rotation.set(0,PI, PI ) ; 
arris10.position.set(0,-1,1) ;

let arris11 =createArrisBox( downColor,leftColor, ) ; //左 

arris11.rotation.set(-PI/2,PI/2, 0 ) ; 
arris11.position.set(-1,-1,0) ;

let arris12 =createArrisBox( downColor,rightColor, ) ; //右

arris12.rotation.set( -PI/2,-PI/2, 0 ) ; 
arris12.position.set(1,-1,0) ; 

中心面

这个就没啥好说的了,和轴承一样简单。 单面,然后分别绕单轴旋转一定角度就能得到其余五个面。

/**
 * 六个中心默认 前面暂且就弄一个面,要仿真的话可以再加四个面
 */

function createCenterBox(color){ let group= new Mesh('c'+id);let mat= new StandardMaterial('sec'+id) ;mat.diffuseColor= color;const frontFace = MeshBuilder.CreatePlane('frontFc'+id) ; frontFace.position.z = -.5 ;frontFace.material = mat ;group.addChild(frontFace);id++group.scaling.set(.9,.9,.9) // 留间隙 return group 
} ;

letfrontC=createCenterBox(frontColor) ;
frontC.position.z =-1 ;

letbackC=createCenterBox(backColor) ;
backC.position.z =1 ;backC.rotation.y = PI ;

letleftC=createCenterBox(leftColor) ;
leftC.position.x = -1 ;leftC.rotation.y = PI/2 ;

letrightC=createCenterBox(rightColor) ;
rightC.position.x = 1 ;rightC.rotation.y = -PI/2 ;

letupC=createCenterBox(upColor) ;
upC.position.y = 1 ;upC.rotation.x = PI/2 ;

letdownC=createCenterBox(downColor) ;
downC.position.y = -1 ;downC.rotation.x = -PI/2 ; 

最后成品如下。

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

ArcGIS基础实验操作100例--实验29矢量数据空间校正

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台:ArcGIS 10.6 实验数据:请访问实验1(传送门) 高级编辑篇--实验29 矢量数据空间校正 目录 一、实验背景 二、实验数据 三、实验步骤 (1&…

android中service实现原理分析

前言: 一开始的目标是解决各种各样的ANR问题的,我们知道,ANR总体上分有四种类型,这四种类型有三种是和四大组件相对应的,所以,如果想了解ANR发生的根因,对安卓四大组件的实现流程是必须要了解的…

Odoo 16 企业版手册 - 库存管理之产品管理

产品管理 记录与产品相关的每个方面对于有效维护库存至关重要。Odoo 库存模块使您可以在数据库中配置新产品,这些产品将有效跟踪和监控所有操作,以加强各自产品的库存管理。库存模块中的产品配置过程与销售和购买模块的流程几乎相似。您将在库存的主菜单…

一步一步学爬虫(4)数据存储之CSV文件存储

一步一步学爬虫(4)数据存储之CSV文件存储4.3 CSV文件存储4.3.1 写入4.3.2 读取4.3.3 总结4.3 CSV文件存储 CSV,全称Comma-Separated Values,中文叫做逗号分隔值或字符分隔值,其文件以纯文本形式存储表格数据。CSV文件…

java.lang.OutOfMemoryError: GC overhead limit exceeded问题分析及解决

一、错误重现 2022-12-29 10:12:07.210 ERROR 73511 --- [nio-8001-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.…

SQL刷题宝典-MySQL速通力扣困难题

📢作者: 小小明-代码实体 📢博客主页:https://blog.csdn.net/as604049322 📢欢迎点赞 👍 收藏 ⭐留言 📝 欢迎讨论! 本手册目录: 文章目录前言Markdown导入数据库python脚…

奇安信 工业互联网安全发展与实践 报告 学习笔记一 欢迎扶正

声明 本文是学习2021工业互联网安全发展与实践分析报告. 下载地址而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 主要观点 工业系统安全漏洞数量增长显著放缓,但超高危漏洞数量却大幅增加。统计显示,2021年,国内外…

Linux 软件包管理器 yum

1.什么是软件包 在Linux下安装软件,一个通常的办法是下载到程序的源代码,并进行编译,得到可执行程序。但是这样太麻烦了,于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在一个服务器上&…

再见2022

大家好,我是bigsai,好久不见。看了上一篇更新时间,大概已经停更近10个月(呜呜后面还会坚持的),在2022的最后一天,这一篇也算是对这一年做个总结。期间也收到一些朋友的问候和鼓励,确实自己在读研期间的前两…

山东大学2022-2023非关系型数据库(Nosql)期末考试

写在前面的话: 今年线上开卷考试,Nosql考试软工(限选课)和大数据(必修课)是一套试题,因此大数据所学的许多内容考试并无涉及。考察点主要以学过的四类Nosql数据库的相关知识为主。 试题如下&…

引用量超1400的经典语义分割方法BiSeNet解读

今天给大家分享语义分割领域非常经典的一篇论文:BiSeNet,该论文发表在了ECCV2018上,引用量超过1400。 开源代码地址:https://github.com/ycszen/TorchSeg 1.动机 语义分割任务,即为图片的每个像素分配一个标签&#…

嵌入式 程序调试之gdb+gdbserver+vscode可视化调试

嵌入式 程序调试之gdbgdbservervscode可视化调试 一、简述 记--使用过visual studio的都知道,它的单步调试真的好用,可以直接在源码下断点,实时查看内存变量、寄存器等相关信息。嵌入式linux开发多用的是gdb, 都是命令行执行的,毕…

python特殊数据类型应用(1)字典类型

目录python中特殊数据类型应用(1)字典类型字典类型定义字典类型注意事项字典类型的访问python中特殊数据类型应用(1)字典类型 python作为最流行的几种开发语言之一,在数据类型上和传统的c、c和java等有很大的不同&…

Typora使用方法

自用,有错误请谅解 tpora破解版使用学习使用: 链接:https://pan.baidu.com/s/1Wj46k3iVIzr-7kwQstp9nQ 提取码:2sa8 来源教程网址:Typora一款 Markdown 编辑器和阅读器 记得更改图片位置,以后就是相对路径…

babel及其使用

什么是Babel? Babel 是一个工具链,由大量的工具包组成,接下来我们逐步了解。主要用于将 ECMAScript 2015 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。 核心库 babel/core B…

‘this’不能用于常量表达式错误(C++)【问题解决】

目录 一、报错问题 1、代码 test.h test.cpp 2、问题描述 二、网上解决思路 三、解决方案 【元旦快乐🌹,新年快乐🎉】 最近在编译程序时出现了“ ‘this’不能用于常量表达式错误(C )”的报错问题,查阅多位博主写的文章后&…

mysql 性能优化

mysql 调优可以从这个四个方面来看 1.性能监控 1.1 show profile for query n 查看具体的sql语句各阶段执行时间 show profiles; show profile for query n; 1.2 performance schema 监控mysql 整个服务器中发生的各种事件。 performance schema 表中的数据不会持久化的磁…

一文搞定垃圾回收的三色标记法

我们之前介绍了各种常见垃圾回收器的基本原理,本小节我们讨论一个更深入的问题——垃圾回收器的底层是如何做的。 在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。…

计算机视觉(CV)领域Transformer最新论文及资源整理分享

Transformer由论文《Attention is All You Need》提出,现在是谷歌云TPU推荐的参考模型。Transformer模型最早是用于机器翻译任务,当时达到了SOTA效果。Transformer改进了RNN最被人诟病的训练慢的缺点,利用self-attention机制实现快速并行。并…

梯度,GD梯度下降,SGD随机梯度下降

前言 羊了,但是依旧生龙活虎。补补之前落下的SGD算法,这个在深度学习中应用广泛。 梯度(Gradient) 方向导数 在梯度之前,非常重要一个概念:方向导数,这里uuu是nnn维向量,代表一个…