背景: 公司产品预演全景参访
实现: 场景查看 锚点标注
关键点: 二维点击转换三维坐标轴、 锚点绘制、场景切换、数据格式设计
未实现: 标注点添加gif 图片
添加视屏未实践 文档中有 添加视频的材质 可以尝试一下
场景查看
一、 创建一个场景
为了真正能够让你的场景借助three.js来进行显示,我们需要以下几个对象:场景、相机和渲染器
,这样我们就能透过摄像机渲染出场景。
直接看官网文档 贼详细 three.js
实操
场景创建
// 场景
const scene = new THREE.Scene();
// 透视相机
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
/**
* 透视相机四个参数 :视野角度
* 长宽比
* 近截面
* 远截面
**/
// 渲染器
const renderer = new THREE.WebGLRenderer();
// 渲染大小
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// 几何体 --------
const geometry = new THREE.BoxGeometry();
// 球体 ----
// SphereGeometry(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength)
// - radius:球体半径
// - widthSegments,
// - heightSegments:水平方向和垂直方向上分段数。widthSegments最小值为3,默认值为8。heightSegments最小值为2,默认值为6。
// - phiStart:水平方向上的起始角,默认值0
// - phiLenght:水平方向上球体曲面覆盖的弧度,默认Math.PI * 2
// - thetaStart : 垂直方向上的起始角, 默认0
// - thetaLength: 垂直方向是球体曲面覆盖的弧度,默认值为Math.PI
const geometry = new THREE.SphereGeometry(500, 60, 40)
// -------------
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );
/**
默认情况下,当我们调用scene.add()的时候,物体将会被添加到(0,0,0)坐标。但将使得摄像机和立方体彼此在一起。为了防止这种情况的发生,我们只需要将摄像机稍微向外移动一些即可。
*/
camera.position.z = 5;
const animate = function () {
requestAnimationFrame( animate );
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render( scene, camera );
};
animate();
图片加载
- 加载图片 贴到纹理图 然后add 创建网格 添加到场景
- 引用图片有两种方式
(第二种方式 引入图片不清晰 应该是canvas 绘图的原因)
let demo = new THREE.TextureLoader().load(vrImgurl)
// 方法一
//防止跨域用canvas作为纹理
let canvas = document.createElement("canvas");
canvas.style.backgroundColor = "rgba(255,255,255,0)";
let context = canvas.getContext("2d");
let img = new Image();
img.src = imgurl
// img.src=''; //绘制全景图
img.onload = function () {
canvas.width = this.width;
canvas.height = this.height;
context.drawImage(img, 0, 0, this.width, this.height);
let texture = new THREE.Texture();
texture.image = canvas;
texture.needsUpdate = true;//开启纹理更新
texture.minFilter = THREE.LinearFilter;//minFilter属性:指定纹理如何缩小
let material = new THREE.MeshBasicMaterial({
map: texture,
transparent: false
});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
};
// 方法二、
let demo = new THREE.TextureLoader().load(vrImgurl)
let material = new THREE.MeshBasicMaterial({
map: demo, // 此处使用 demo 的参数 图片更为清晰
transparent: false,
})
mesh = new THREE.Mesh(geometry, material)
// 几何体 材料(渲染图)
scene.add(mesh)
}
场景切换
1、先获取挂载元素的子节点
2、由于mesh 和scene 是全局定义 所以需要清除 mesh && scene.remove(mesh)
3、重新实例化即可
// 初始化先删除子节点
let container = document.getElementById('container')
if (container.childNodes.length) {
container.removeChild(container.childNodes[0])
}
轨道控制
`在轨道控制中 全景图方向与鼠标拖拽方向一致 如果需要翻转 则在源码找到`
function rotateLeft( angle ) {
sphericalDelta.theta -= angle;
}
function rotateUp( angle ) {
sphericalDelta.phi -= angle;
}
// ---- 修改为 即可修正方向
function rotateLeft( angle ) {
sphericalDelta.theta += angle;
}
function rotateUp( angle ) {
sphericalDelta.phi += angle;
}
import OrbitControls from 'three-orbitcontrols'
// 在场景初始化完成后 初始化控制器
// 初始化控制器
const initcontrols = () => {
controls = new OrbitControls(camera, renderer.domElement)
console.log(controls,'--controls')
//是否可以缩放
controls.enableZoom = false
//是否自动旋转
controls.autoRotate = autoRotate // 动态控制是否制动旋转、
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
}
锚点添加 关键点 二维视图获取三维场景中的点击坐标 (THREE.Raycaster)
1、获取点击时的坐标位置
1-1、 创建场景时绑定 点击事件
1-2、在当前相机所正视的世界方向建立 三维向量vector3
1-3、 根据页面展示大小 设置该向量的x、y 和 z 分量。
1-4、(重点)使用 光线投射Raycaster 计算鼠标在三维坐标中点击的坐标位置
1-5、传入鼠标点击点击坐标在三维坐标中的中位置信息
2、绘制需要标注的精灵 Sprite
在下面代码中可以 有具体添加步骤
3、将绘制的精灵添加到 场景 全局 scene
// 鼠標点击添加一个 确定点击位置 -- 锚点 ---待配置 热点图片
const onDocumentMouseDown = event => {
/**
* 1、 camera.target 当前相机所正视的世界空间方向 赋值给 vector
* 2、根据配置页面 展示的宽高值 设置XYZ 轴
* 3、 vector.unproject(camera) 在投影中使用的摄像机。
* 4、 使用 光线投射Raycaster 计算鼠标在三维坐标中点击的坐标位置
* 这将创建一个新的raycaster对象。
* let raycaster = new THREE.Raycaster(
* camera.position,
* vector.sub(camera.position).normalize() //初始化
* )
* Raycaster( origin : Vector3, direction : Vector3, near : Float, far : Float ) {
origin —— 光线投射的原点向量。
direction —— 向射线提供方向的方向向量,应当被标准化。
near —— 返回的所有结果比near远。near不能为负值,其默认值为0。
far —— 返回的所有结果都比far近。far不能小于near,其默认值为Infinity(正无穷。)
*
*/
isUserInteracting = true
if (forType === 'Equirectangular') {
event.preventDefault()
// let vector = new THREE.Vector3() //三维坐标对象
let vector = camera.target
console.log(vector, 'vector预计是坐标轴的位置')
vector.set(
((event.clientX - 248) / (window.innerWidth - 248)) * 2 - 1,
-((event.clientY - 32) / (window.innerHeight - 32)) * 2 + 1,
0.5
)
// 在投影中使用的摄像机。
vector.unproject(camera)
// 这将创建一个新的raycaster对象。
let raycaster = new THREE.Raycaster(
camera.position,
vector.sub(camera.position).normalize() //初始化 光线投射的原点向量
)
raycaster.camera = camera
// 得到 点击的坐标 或 点击的标注点
// intersects 每项中的object 的type 可以分辨 点击的是标注还是 场景图
let intersects = raycaster.intersectObjects(scene.children)
//如果绘制热点属于激活状态
// 此处需要判断 是否有两个坐标为0
let isOnShaft = []
Object.keys(intersects[0].point).forEach(v => {
if (intersects[0].point[v] === 0) {
isOnShaft.push(1)
}
})
//---------------------------添加標注-----------------------------------------
// 打开添加热点 true 坐标轴中是否存在两坐标为0 打开删除热点开关 为fasle
if (refIsHotspot.current && isOnShaft.length < 2 && !refIsDelete.current) {
// 绘制热点
// 绘制图片有两种方式
// 一种直接引入 new img
// 一种使用 canvas
/**
let canvas = document.createElement('canvas')
canvas.style.backgroundColor = 'rgba(255,255,255,0)'
let context = canvas.getContext('2d')
canvas.width = 128
canvas.height = 128
context.drawImage(img, 0, 0, 128, 128)
*/
let img = new Image()
img.src = hotspot //( 标注使用的图片) 也可以使用canvas 绘制文字
img.onload = function () {
let texture = new THREE.Texture(img)
texture.needsUpdate = true
texture.minFilter = THREE.LinearFilter
var spriteMaterial = new THREE.SpriteMaterial({
map: texture,
transparent: false,
})
// 创建一个 sprite 物体
var sprite = new THREE.Sprite(spriteMaterial)
sprite.scale.set(30, 30, 30)
let rate = 0.8
var endV = new THREE.Vector3(
intersects[0].point.x * rate,
intersects[0].point.y * rate,
intersects[0].point.z * rate
)
sprite.position.copy(endV)
scene.add(sprite)
// addHotspot(intersects[0].point) //同步到全局数据
}
//移除热点
} else {
if (!refIsDelete.current) return
if (intersects.length > 0) {
const target = intersects[0]
console.log(!refIsHotspot.current, refIsDelete.current, '删除打印结果')
try {
if (target.object && target.object.type.length > 0) {
if (target.object.type.toLowerCase() === 'sprite') {
scene.remove(target.object)
}
}
} catch (e) {
console.log(e)
}
}
}
}
}
初始化 绘制热点
//绘制多个跳转热点
const drawJumpHotSpots = (variable, newsrc) => {
console.log(variable, '锚点坐标轴数据 参数')
variable.forEach(item => {
let position = item.point
// TextureLoader 异步记载图片
var texture = new THREE.TextureLoader().load(gif)
// SpriteMaterial 材质
var spriteMaterial = new THREE.SpriteMaterial({
map: texture,
transparent: true,
})
// 物体 Sprite
var sprite = new THREE.Sprite(spriteMaterial)
sprite.scale.set(30, 30, 30)
/**
* 此处添加自定义属性 不能跟原有属性重复避免报错
* name: 添加锚点名称
* ids: 唯一ID
* iconUrl: 图标
*/
sprite.name = item.name
sprite.ids = item.id
sprite.iconUrl = ''
let rate = 0.8
var endV = new THREE.Vector3(position.x * rate, position.y * rate, position.z * rate)
sprite.position.copy(endV)
scene.add(sprite)
})
}
操作
纹理图引入图片 ( 材质 加载图片)
// 方案一、
let texture = new THREE.TextureLoader().load(img)
// // TextureLoader 异步记载图片
var texture = new THREE.TextureLoader().load(gif)
// SpriteMaterial 材质
var spriteMaterial = new THREE.SpriteMaterial({
map: texture,
transparent: true,
})
// 方案二、
let texture = new THREE.Texture(img)
texture.needsUpdate = true
texture.minFilter = THREE.LinearFilter
var spriteMaterial = new THREE.SpriteMaterial({
map: texture,
transparent: false,
})
// 创建一个 sprite 物体
var sprite = new THREE.Sprite(spriteMaterial)
鼠标点击二维坐标 转换三维坐标轴 信息 绘制标注点 ( Raycaster)
// 我其实也没太理解里面的 API的具体参数
// camera.target 全局声明的 透视相机
// (event.clientX - 248) / (window.innerWidth - 248)) * 2 - 1
// 页面布局 全景图距离左方 248px
// (event.clientY - 32) / (window.innerHeight - 32)) * 2 + 1
// 页面布局 全景图距离左方32pX
// scene.children >>> scene 为全局声明 let scene = new THREE.Scene()
let vector = camera.target
console.log(vector, 'vector预计是坐标轴的位置')
vector.set(
((event.clientX - 248) / (window.innerWidth - 248)) * 2 - 1,
-((event.clientY - 32) / (window.innerHeight - 32)) * 2 + 1,
0.5
)
// 在投影中使用的摄像机。
vector.unproject(camera)
// 这将创建一个新的raycaster对象。
let raycaster = new THREE.Raycaster(
camera.position,
vector.sub(camera.position).normalize() //初始化 光线投射的原点向量
)
raycaster.camera = camera
// 得到 点击的坐标 或 点击的标注点
// intersects 每项中的object 的type 可以分辨 点击的是标注还是 场景图
点击的坐标点上的 物体
let intersects = raycaster.intersectObjects(scene.children)
//如果绘制热点属于激活状态
// 此处需要判断 是否有两个坐标为0 两个为0 时 添加的锚点会很大
let isOnShaft = [] // 是否在坐标轴上
Object.keys(intersects[0].point).forEach(v => {
if (intersects[0].point[v] === 0) {
isOnShaft.push('靓仔')
}
})
坐标轴展示 开发实用 (AxesHelper)
// 三维坐标轴 坐标轴长度
var axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelpe r);
数据格式设计
panoramicData: [
{
name: '会所',
id: '2102271653',
url: 'huisuo',
active: true,
// 锚点信息
anchorPoint: [
{
point: {
x: 180.01349809670057,
y: 15.79023683858044,
z: 465.07418151652786,
},
id: '2102091411',
name: '海边',
iconUrl: 'haibian',
},
{
point: {
x: 247.4793362659326,
y: -189.1800093391692,
z: 390.2798175065487,
},
id: '202102181619',
name: '客厅',
iconUrl: 'keting',
},
],
},
{
name: '海边',
id: '2102091411',
url: 'haibian',
active: false,
autoRotate: false,
// 锚点信息
anchorPoint: [
{
point: {
x: 374.5454984418328,
y: -5.458415157221607,
z: 330.55353704327746,
},
id: '202102181621',
name: '豪宅',
iconUrl: 'haozhai',
},
{
point: {
x: 140.18787952741366,
y: -97.9969695393665,
z: 468.933553788003,
},
id: '202102181619',
name: '客厅',
iconUrl: 'keting',
},
],
},
{
name: '客厅',
id: '202102181619',
url: 'keting',
active: false,
autoRotate: false,
// 锚点信息
anchorPoint: [
{
point: {
x: 481.8527362463277,
y: -24.6389543862957,
z: 127.17004633132723,
},
id: '2102271653',
name: '会所',
iconUrl: 'huisuo',
},
{
point: {
x: 347.09301641855546,
y: -109.56249057173801,
z: 341.54549425701236,
},
id: '2102091411',
name: '海边',
iconUrl: 'haibian',
},
],
},
{
name: '豪宅',
id: '202102181621',
url: 'haozhai',
active: false,
autoRotate: false,
// 锚点信息
anchorPoint: [
{
point: {
x: 85.2120582814672,
y: -0.4428222360704279,
z: 492.0249309249495,
},
id: '2102271653',
name: '会所',
iconUrl: 'huisuo',
},
{
point: {
x: 441.06070438164795,
y: -157.51582618415583,
z: 173.01486267642798,
},
id: '2102091411',
name: '海边',
iconUrl: 'haibian',
},
],
},
],