实现模型贴图的移动缩放旋转

news2024/11/19 0:43:32

技术:threejs+canvas+fabric

效果图:

原理:threejs中没有局部贴图的效果,只能通过map 的方式贴到模型上,所以说换一种方式来实现,通过canvas+fabric来实现图片的移动缩放旋转,然后将整个画布以map 的形式放到模型材质上,实现局部贴图的效果

直接上代码:

<template>
    <div id="c-left">
      <input type="file" @change="handleFileChange" accept=".png" />
      <div id="container"></div>
    </div>
    <div id="c-right">
      <canvas id="canvas" width="512" height="512"></canvas>
    </div>
</template>
  
<script>
import { fabric } from 'fabric'
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
  


// oss上传相关配置
let OSS = require('ali-oss')
let client = new OSS({
    region: 'oss-cn-beijing',
    accessKeyId: 'xxxxx',
    accessKeySecret: 'xxxxx',
    bucket: 'xxxxx'
})

// 设置场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xfffff0);
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
const dirLight1 = new THREE.DirectionalLight( 0xffffff, 2.5); 
dirLight1.position.set( 0, 0.5, 1 );
scene.add( dirLight1 );

const dirLight2 = new THREE.DirectionalLight( 0xffffff, 2.5); 
dirLight2.position.set( 0, 0.5, -1 );
scene.add( dirLight2 );

const dirLight3 = new THREE.DirectionalLight( 0xffffff, 2.5 );
dirLight3.position.set( 0, -0.5, 0 );
scene.add( dirLight3 );

const n = 2
// 设置视角
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth/n / window.innerHeight,
  0.1,
  1000
);
camera.position.set(0, 5, 10);
// 随机名称
function generateRandomFileName() {
  const date = new Date().toISOString().replace(/[-:.TZ]/g, '');
  const randomPart = Math.random().toString(36).substr(2, 6);
  return `${date}-${randomPart}`;
}

let selectedImage = null
export default {
  data(){
    return {
      canvas_s:null,
      image_url:null,
    }
  },
  methods:{
    async handleFileChange(event) {
      const file = event.target.files[0];
      if (!file || file.type!== 'image/png') {
          alert('请选择 PNG 格式的图片!');
          return;
      }
      const fileName = generateRandomFileName();
      await client.put(`m2_photos/${fileName}`, file);
      const url = client.signatureUrl(`m2_photos/${fileName}`);
      console.log("url为: ", url);
      this.image_url = url
    },
    init(){
      let flag = {x:false}; 
      // 创建渲染器
      const renderer = new THREE.WebGLRenderer({
          preserveDrawingBuffer: true,
          antialias: true,
      });
      const container = document.getElementById("container");
      container.appendChild(renderer.domElement);
      var s = new fabric.Canvas('canvas');
      s.backgroundColor = 'rgb(100, 255, 255)'; // 设置画布背景
      this.canvas_s = s
      // 创建轨道控制器
      const controls = new OrbitControls(camera, renderer.domElement);
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      renderer.outputEncoding = THREE.sRGBEncoding;
      // 开启场景中的阴影贴图
      renderer.shadowMap.enabled = true;
      // 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
      controls.enableDamping = true;
      
      renderer.setSize(window.innerWidth/n, window.innerHeight);
      
      // 添加坐标系
      const axesHelper = new THREE.AxesHelper(10);
      scene.add(axesHelper);
      
      // 异步添加图片,能够实现图片的任意交互
      fabric.Image.fromURL('xxxxxxx', (oImg)=> {
          oImg.scale(0.1);
          var canvasWidth = s.width;
          var canvasHeight = s.height;
          // 计算图片放置在正中间的位置
          var left = canvasWidth / 2 ;
          var top = canvasHeight / 2 ;
          oImg.set({
              left: left - 80,  
              top: top -40  
          });
          console.log("oImg : ",oImg);
          s.add(oImg);
      }, {crossOrigin: 'anonymous'});

      // 定时任务
      setInterval(()=>{
        if (this.image_url) {
          fabric.Image.fromURL(this.image_url, (oImg)=> {
            oImg.scale(0.1);
            var canvasWidth = s.width;
            var canvasHeight = s.height;
            // 计算图片放置在正中间的位置
            var left = canvasWidth / 2 ;
            var top = canvasHeight / 2 ;
            oImg.set({
                left: left - 80,  
                top: top -40  
            });
            console.log("oImg : ",oImg);
            s.add(oImg);
          }, {crossOrigin: 'anonymous'});
          this.image_url = null
        }
      },1000)

      var texture = new THREE.Texture(document.getElementById("canvas"));
      texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
      const mapTexture = new THREE.TextureLoader().load('/statisc/fabric004.png')
      const loader = new OBJLoader();
      loader.load('模型的位置', (object) => {
          object.traverse((child) => {
              child.material = new THREE.MeshLambertMaterial({ 
                  color:0xffffff,
                  side:THREE.DoubleSide,
                  // transparent:false,
                  // opacity:1,
                  bumpMap:mapTexture,
                  // alphaMap:mapTexture,
                  bumpScale:1,
                  // emissive:0x404040
              });
              child.material.map = texture;
              child.material.map.minFilter = THREE.LinearFilter
              child.material.map.colorSpace = 'srgb'
              console.log("map",child.material.map);
          });
          object.scale.set(0.1, 0.1, 0.1); // 变小一点
          object.position.set(0, -10, 0)
          scene.add(object);
          
          // 新增:为模型添加点击事件监听
          renderer.domElement.addEventListener('click', onModelClick);
      }, () => {
      }, () => {
      });

      // 按键设置
      document.addEventListener('keydown',function (event) {
        if (flag.x) {
          if (event.key === 's') {
            selectedImage.top += 5;
          }else if(event.key === 'a'){
            selectedImage.left -= 5;
          }else if( event.key === 'd'){
            selectedImage.left += 5;
          }else if(event.key === 'w'){
            selectedImage.top -= 5;
          }else if(event.key === 'q'){
            selectedImage.angle -= 5
          }else if(event.key === 'e'){
            selectedImage.angle += 5
          }else if(event.key === '6'){
            selectedImage.scaleX += 0.01
          }else if(event.key === '4'){
            selectedImage.scaleX -= 0.01
          }else if(event.key === '2'){
            selectedImage.scaleY += 0.01
          }else if(event.key === '8'){
            selectedImage.scaleY -= 0.01
          }else if(event.key === '3'){
            selectedImage.scaleY += 0.01
            selectedImage.scaleX += 0.01
          }else if(event.key === '7'){
            selectedImage.scaleY -= 0.01
            selectedImage.scaleX -= 0.01
          }else if(event.key === 'Backspace'){
            s.remove(selectedImage)
          }else if(event.key === 'ArrowUp'){
            s.bringForward(selectedImage)
          }else if(event.key === 'ArrowDown'){
            s.sendBackwards(selectedImage)
          }
          s.renderAll();
        }
      })
      
      const geometry = new THREE.BoxGeometry(1, 1, 1);
      const material = new THREE.MeshBasicMaterial({ map:texture });
      const cube = new THREE.Mesh(geometry, material);
      scene.add(cube);
      
      
      function render() {
          controls.update();
          texture.needsUpdate = true
          renderer.render(scene, camera);
          // 渲染下一帧的时候就会调用render函数
          requestAnimationFrame(render);
      }
      
      render();
      
      var raycaster = new THREE.Raycaster();
      var mouse = new THREE.Vector2();

      // 鼠标点击事件
      function onModelClick(event) {  
        flag.x = false
        event.preventDefault();
        // pos 在场景图像上的位置
        var pos = [event.clientX,event.clientY]
        var rect = container.getBoundingClientRect();
        mouse.x = ((pos[0] - rect.left) / rect.width) *2-1
        mouse.y = -((pos[1] - rect.top) / rect.height) *2+1
        raycaster.setFromCamera(mouse, camera);
        // 通过射线获得场景中的对象
        var intersects = raycaster.intersectObjects(scene.children);
        if (intersects.length > 0 && intersects[0].uv) {
          var uv = intersects[0].uv;
          intersects[0].object.material.map.transformUv(uv)
          // 512表示画布的宽和高都是512
          var x = Math.round(uv.x * rect.width/(1+0.002*(rect.width-512))); 
          var y = Math.round(uv.y * rect.height/(1+0.002*(rect.height-512)));
          const positionOnScene = {x,y}
          selectCanvas(positionOnScene,flag)
        }
        if (!flag.x) {
          s.discardActiveObject();
          s.renderAll();
        }
      }
      // 选中模型中的图片
      function selectCanvas(point,flag) {
        const objects = s.getObjects();
        for (let i = objects.length - 1; i >= 0; i--) {
          const obj = objects[i];
          if (obj.containsPoint(point)) {
            s.setActiveObject(obj);  // 设置图形为选中状态
            flag.x = true;  // 标记有图形被选中
            selectedImage = obj
            s.renderAll();
            break; 
          }
        }
      }
    }
  },
  mounted() {
    this.init();
  },
}
  
  
</script>
  
<style>
#c-left, #c-right {
position: relative;
display: inline-block;
height: 100%;
width: 50%;
}

#c-right {
float: right;
/* display: none; */
}
</style>

我是使用的vue3,同时还包含了oss的图片上传功能以及threejs 的反射效果,当点击模型上的图片时,即可选中图片,并通过wasd移动图片位置,qe旋转,123456789各个位置的缩放,还是很有趣的~

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

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

相关文章

APP项目测试 之 APP性能测试-- 性能测试工具(SoloPi工具)

1.SoloPi简介 &#xff08;1&#xff09;什么是SoloPi&#xff1f; SoloPi&#xff1a; 是一个无线化、非侵入式的 Android 自动化工具 &#xff0c;具备 录制回放、性能测试 等功能。 &#xff08;2&#xff09;SoloPi的作用是什么&#xff1f; 基础性能测试&#xff1a;能够…

STM32-I2C硬件外设

本博文建议与我上一篇I2C 通信协议​​​​​​共同理解 合成一套关于I2C软硬件体系 STM32内部集成了硬件I2C收发电路&#xff0c;可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能&#xff0c;减轻CPU的负担 特点&#xff1a; 多主机功能&#x…

[word] Word如何快速生成一段文本 #知识分享#学习方法

Word如何快速生成一段文本 Word如何快速生成一段文本&#xff1f;有时候我们会用一大段文字来做一些功能测试&#xff0c;不少朋友的做法就是脸滚键盘&#xff0c;一顿乱按&#xff0c;这样看起来文笔不通&#xff0c;看着也会比较难受&#xff0c;测试功能的效果也不怎么理想…

【全面讲解下Foxit Reader】

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

微信小程序毕业设计-学生实习与就业管理系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

一款免费的PDF编辑软件,内置了OCR功能,识别效果好

主要是想分享给大家他的OCR功能&#xff0c;面对无法编辑的PDF或者图片&#xff0c;如何批量的转成文字或者带有格式的word文档&#xff0c;很多时候或者很多工具做的不理想&#xff0c;今天分享的这款工具应该是目前为止&#xff0c;我遇到的最好的批量OCR工具。他不是简单的O…

spring boot实现短信验证码功能

1、到阿里云网站申请 https://market.aliyun.com/products/5700000 2/cmapi00046920.html2、配置文件&#xff0c;可申请测试 sms:app-code: xxxxxxxxxtemplate-id: xxxxxxx3、使用restTemplate用于第三方接口调用 package com.example.rsocketclient.config;import org.spr…

笔记14:程序中的循环结构

生活中的循环现象&#xff1a; -日复一日&#xff0c;年复一年 -春夏秋冬&#xff0c;四季交替 -周日&#xff0c;周一&#xff0c;周二&#xff0c;周三&#xff0c;周四&#xff0c;周五&#xff0c;周六 -人生是一个轮回&#xff0c;多年后&#xff0c;又会回到最初的原点 …

Python学习从0开始——Kaggle实践可视化001

Python学习从0开始——Kaggle实践可视化001 一、创建和加载数据集二、数据预处理1.按name检查&#xff0c;处理重复值&#xff08;查重&#xff09;2.查看存在缺失值的列并处理&#xff08;缺失值处理&#xff09;2.1按行或列查看2.2无法推测的数据2.3可由其它列推测的数据 3.拆…

大数据Spark 面经

1: Spark 整体架构 Spark 是新一代的大数据处理引擎&#xff0c;支持批处理和流处理&#xff0c;也还支持各种机器学习和图计算&#xff0c;它就是一个Master-worker 架构&#xff0c;所以整个的架构就如下所示&#xff1a; 2: Spark 任务提交命令 一般我们使用shell 命令提…

【HICE】web服务搭建之仓库

1.首先将1.conf变成vhost&#xff0c;从而使监听号只有最普通的&#xff0c;并且进行更新。 2.挂载 mount /dev/sr0 /var/www/html 3.更改本地仓库路径 4.测试&#xff1a;下载软件包&#xff0c;在删除 5.删除软件包在取消挂载&#xff0c;在下载软件包失败

计算机网络-IP组播基础

一、概述 在前面的学习交换机和路由协议&#xff0c;二层通信是数据链路层间通信&#xff0c;在同一个广播域间通过源MAC地址和目的MAC地址进行通信&#xff0c;当两台主机第一次通信由于不清楚目的MAC地址需要进行广播泛洪&#xff0c;目的主机回复自身MAC地址&#xff0c;然后…

C++:this指针到底是什么东西

一、this指针概述 在C中&#xff0c;this是一个隐含的指针&#xff0c;它指向当前正在被调用的函数的对象实例。当你在一个成员函数内部引用self, me, 或者是无名的"this"时&#xff0c;实际上是访问了这个特殊的变量。this通常用于区分函数参数和局部变量&#xff0…

linux驱动编程 - kfifo先进先出队列

简介&#xff1a; kfifo是Linux Kernel里面的一个 FIFO&#xff08;先进先出&#xff09;数据结构&#xff0c;它采用环形循环队列的数据结构来实现&#xff0c;提供一个无边界的字节流服务&#xff0c;并且使用并行无锁编程技术&#xff0c;即当它用于只有一个入队线程和一个出…

机器学习筑基篇,​Ubuntu 24.04 编译安装 Python 及多版本切换

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] Ubuntu 24.04 编译安装最新Python及多版本切换 描述&#xff1a;说到机器学习&#xff0c;人工智能&#xff0c;深度学习不免会提到Python这一门编程语言&#xff08;人生苦短&#xff0c;及时Pyt…

Redis的zset的zrem命令可以做到O(1)吗?

事情是这样的&#xff0c;当我用zrem命令去移除value的时候&#xff0c;我知道他之前会做的几个步骤 1、查找这个value对应的score&#xff08;通过zset中的dict&#xff09;2、根据这个score查找到跳表中的节点3、删除这个节点 我就想了一下为什么dict为什么要保存score呢&a…

Caffeinated for Mac v2.0.6 Mac防休眠应用 兼容 M1/M2/M3

Caffeinated 可以防止您的 Mac 进入休眠状态、屏幕变暗或者启动屏幕保护。 应用介绍 您的屏幕是否总是在您不希望的时候变暗&#xff1f;那么Caffeinated就是您解决这个大麻烦的最好工具啦。Caffeinated是在Caffeine这个非常便捷、有用的工具的基础上开发而来的。Caffeinated…

insert阻塞了insert?

一、发现问题 在arms监控页面看到某条insert语句的执行时长达到了431毫秒。 数据库中存在&#xff0c;insert语句受到了行锁阻塞&#xff0c;而阻塞的源头也在执行同样的insert语句&#xff0c;同样都是对表USERSYS_TASK_USER_LOG_TEMP01的插入操作&#xff0c;很是费解。 二…

vue2-vue3响应式原理

我们先来看一下响应式意味着什么&#xff1f;我们来看一段代码&#xff1a; m有一个初始化的值&#xff0c;有一段代码使用了这个值&#xff1b;那么在m有一个新的值时&#xff0c;这段代码可以自动重新执行&#xff1b; let m 20 console.log(m) console.log(m * 2)m 40上…

深圳航空顶象验证码逆向,和百度验证码训练思路

声明(lianxi a15018601872) 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 前言(lianxi a…