此前探索过流动线的实现方式,看到另一个案例,学习了另一种实现流动道路的方法
用到了一个开源计算库
https://github.com/yszhao91/xtorcga,实现效果
核心就是基于 cga.extrudeToGeometryBuffer ,将道路生成两份,然后不断改变贴图的位置即可
前序逻辑都是在做一些数据处理,后续有需要再研究数据结构吧。
json文件可从此处取得
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../js/three.min.js"></script>
<script src="../js/OrbitControls.js"></script>
<script src="../js/Init.js"></script>
<script src="jquery-1.10.2.min.js"></script>
<style>
#box{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
</head>
<body>
<div class="ys-absolute-container" id="box" style="overflow: hidden"></div>
<script src="../js/plugins/MeshLine.js"></script>
<script src="cga.js"></script>
<script>
let el=document.getElementById('box')
let app=new THREE.inithree(el,{
axes:true,
})
let scene=app.scene
let renderer=app.renderer
let camera=app.camera
let controls=app.controls
camera.position.set(50, 50, 50)
camera.lookAt(new THREE.Vector3(0, 0, 0))
var ambientLight = new THREE.AmbientLight(0xffffff,0.9);
scene.add(ambientLight);
// 参数依次为color、强度、距离、角度,penumbra,decay
var spotLight = new THREE.SpotLight(0x00ff00);
spotLight.position.set(30,25,-10)
scene.add(spotLight);
function toSVGData(segments, points) {
var str = "";
var pos = 0
for (let i = 0; i < segments.length; i++)
{
switch (segments[i])
{
case 1:
str += "M " + points[pos].x + " " + points[pos].y + " ";
pos += 1;
break;
case 2:
str += "L " + points[pos].x + " " + points[pos].y + " ";
pos += 1;
break;
case 3:
str += "Q " + points[pos].x + " " + points[pos].y + " "
+ points[pos + 1].x + " " + points[pos + 1].y + " ";
pos += 2;
break;
case 4:
str += "C " + points[pos].x + " " + points[pos].y + " "
+ points[pos + 1].x + " " + points[pos + 1].y + " "
+ points[pos + 2].x + " " + points[pos + 2].y + " ";
pos += 3;
break;
case 5:
str += "Z ";
break;
default:
break;
}
}
return str;
}
function parseFloats(string) {
var array = string.split(/[\s,]+|(?=\s?[+\-])/);
for (var i = 0; i < array.length; i++)
{
var number = array[i];
// Handle values like 48.6037.7.8
// TODO Find a regex for this
if (number.indexOf('.') !== number.lastIndexOf('.'))
{
var split = number.split('.');
for (var s = 2; s < split.length; s++)
{
array.splice(i + s - 1, 0, '0.' + split[s]);
}
}
array[i] = parseFloat(number);
}
return array;
}
function parsePathNode(data) {
var path = new THREE.ShapePath();
var point = new THREE.Vector2();
var control = new THREE.Vector2();
var firstPoint = new THREE.Vector2();
var isFirstPoint = true;
var doSetFirstPoint = false;
window.cga=cga
var d = data;
// console.log( d );
var commands = d.match(/[a-df-z][^a-df-z]*/ig);
for (var i = 0, l = commands.length; i < l; i++)
{
var command = commands[i];
var type = command.charAt(0);
var data = command.substr(1).trim();
if (isFirstPoint === true)
{
doSetFirstPoint = true;
isFirstPoint = false;
}
switch (type)
{
case 'M':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2)
{
point.x = numbers[j + 0];
point.y = numbers[j + 1];
control.x = point.x;
control.y = point.y;
if (j === 0)
{
path.moveTo(point.x, point.y);
} else
{
path.lineTo(point.x, point.y);
}
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'H':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j++)
{
point.x = numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'V':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j++)
{
point.y = numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'L':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2)
{
point.x = numbers[j + 0];
point.y = numbers[j + 1];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'C':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 6)
{
path.bezierCurveTo(
numbers[j + 0],
numbers[j + 1],
numbers[j + 2],
numbers[j + 3],
numbers[j + 4],
numbers[j + 5]
);
control.x = numbers[j + 2];
control.y = numbers[j + 3];
point.x = numbers[j + 4];
point.y = numbers[j + 5];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'S':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 4)
{
path.bezierCurveTo(
getReflection(point.x, control.x),
getReflection(point.y, control.y),
numbers[j + 0],
numbers[j + 1],
numbers[j + 2],
numbers[j + 3]
);
control.x = numbers[j + 0];
control.y = numbers[j + 1];
point.x = numbers[j + 2];
point.y = numbers[j + 3];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'Q':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 4)
{
path.quadraticCurveTo(
numbers[j + 0],
numbers[j + 1],
numbers[j + 2],
numbers[j + 3]
);
control.x = numbers[j + 0];
control.y = numbers[j + 1];
point.x = numbers[j + 2];
point.y = numbers[j + 3];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'T':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2)
{
var rx = getReflection(point.x, control.x);
var ry = getReflection(point.y, control.y);
path.quadraticCurveTo(
rx,
ry,
numbers[j + 0],
numbers[j + 1]
);
control.x = rx;
control.y = ry;
point.x = numbers[j + 0];
point.y = numbers[j + 1];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'A':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 7)
{
var start = point.clone();
point.x = numbers[j + 5];
point.y = numbers[j + 6];
control.x = point.x;
control.y = point.y;
parseArcCommand(
path, numbers[j], numbers[j + 1], numbers[j + 2], numbers[j + 3], numbers[j + 4], start, point
);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'm':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2)
{
point.x += numbers[j + 0];
point.y += numbers[j + 1];
control.x = point.x;
control.y = point.y;
if (j === 0)
{
path.moveTo(point.x, point.y);
} else
{
path.lineTo(point.x, point.y);
}
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'h':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j++)
{
point.x += numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'v':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j++)
{
point.y += numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'l':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2)
{
point.x += numbers[j + 0];
point.y += numbers[j + 1];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'c':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 6)
{
path.bezierCurveTo(
point.x + numbers[j + 0],
point.y + numbers[j + 1],
point.x + numbers[j + 2],
point.y + numbers[j + 3],
point.x + numbers[j + 4],
point.y + numbers[j + 5]
);
control.x = point.x + numbers[j + 2];
control.y = point.y + numbers[j + 3];
point.x += numbers[j + 4];
point.y += numbers[j + 5];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 's':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 4)
{
path.bezierCurveTo(
getReflection(point.x, control.x),
getReflection(point.y, control.y),
point.x + numbers[j + 0],
point.y + numbers[j + 1],
point.x + numbers[j + 2],
point.y + numbers[j + 3]
);
control.x = point.x + numbers[j + 0];
control.y = point.y + numbers[j + 1];
point.x += numbers[j + 2];
point.y += numbers[j + 3];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'q':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 4)
{
path.quadraticCurveTo(
point.x + numbers[j + 0],
point.y + numbers[j + 1],
point.x + numbers[j + 2],
point.y + numbers[j + 3]
);
control.x = point.x + numbers[j + 0];
control.y = point.y + numbers[j + 1];
point.x += numbers[j + 2];
point.y += numbers[j + 3];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 't':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2)
{
var rx = getReflection(point.x, control.x);
var ry = getReflection(point.y, control.y);
path.quadraticCurveTo(
rx,
ry,
point.x + numbers[j + 0],
point.y + numbers[j + 1]
);
control.x = rx;
control.y = ry;
point.x = point.x + numbers[j + 0];
point.y = point.y + numbers[j + 1];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'a':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 7)
{
var start = point.clone();
point.x += numbers[j + 5];
point.y += numbers[j + 6];
control.x = point.x;
control.y = point.y;
parseArcCommand(
path, numbers[j], numbers[j + 1], numbers[j + 2], numbers[j + 3], numbers[j + 4], start, point
);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'Z':
case 'z':
path.currentPath.autoClose = true;
if (path.currentPath.curves.length > 0)
{
// Reset point to beginning of Path
point.copy(firstPoint);
path.currentPath.currentPoint.copy(point);
isFirstPoint = true;
}
break;
default:
console.warn(command);
}
// console.log( type, parseFloats( data ), parseFloats( data ).length )
doSetFirstPoint = false;
}
return path;
}
$.ajax({url:"cs.json",success:function(data){
var paths = [data.d[25],
data.d[26],
data.d[27]];
var strs = []
for (let i = 0; i < paths.length; i++)
{
const node = paths[i];
var pts = node.p.points.__a
var sgs = node.p.segments.__a
var str = toSVGData(sgs, pts)
strs.push(str)
}
var roadMaterial = new THREE.MeshBasicMaterial({
transparent: true,
depthWrite: false
})
const textureLoader = new THREE.TextureLoader();
let url='../images/traffic_01.png'
var roadMap = textureLoader.load(url);
roadMap.wrapS = THREE.RepeatWrapping
roadMap.wrapT = THREE.RepeatWrapping
roadMap.rotation = Math.PI / 2;
roadMap.offset.set(0.5, 0.5);
var roadMap1 = textureLoader.load(url);
roadMap1.wrapS = THREE.RepeatWrapping
roadMap1.wrapT = THREE.RepeatWrapping
roadMap1.rotation = Math.PI / 2;
roadMap1.offset.set(0.5, 0.5);
var roadMap2 =textureLoader.load(url);
roadMap2.wrapS = THREE.RepeatWrapping
roadMap2.wrapT = THREE.RepeatWrapping
roadMap2.rotation = Math.PI / 2;
roadMap2.offset.set(0.5, 0.5);
var roadmats = [roadMaterial, roadMaterial.clone(), roadMaterial.clone()]
roadmats[0].map = roadMap;
roadmats[1].map = roadMap1;
roadmats[2].map = roadMap2;
var roadGroup = new THREE.Group();
roadGroup.position.set(66.94476, 0, -235.22057);
var roaddata = [{
"position": {
"x": 66.94476,
"y": -235.22057
},
"thickness": 10
},
{
"position": {
"x": 7.04712,
"y": -284.96567
},
"thickness": 14
},
{
"position": {
"x": -42.85882,
"y": -603.58046
},
"thickness": 20
}]
for (let p = 0; p < strs.length; p++)
{
var path = parsePathNode(strs[p])
var group = new THREE.Group();
group.position.set(roaddata[p].position.x, 0, roaddata[p].position.y)
for (var j = 0, jl = path.subPaths.length; j < jl; j++)
{
var subPath = path.subPaths[j];
var points = subPath.getPoints();
var v3ps = [];
for (let k = 0; k < points.length; k++)
{
const p2o = points[k];
v3ps.push(new THREE.Vector3(p2o.x, 0, p2o.y))
}
;
var geometry = cga.extrudeToGeometryBuffer(
[
cga.v3( 0, 0,-roaddata[p].thickness / 2,),
cga.v3(0, 0,roaddata[p].thickness / 2,)]
, v3ps, {
normal: cga.v3(0, 0, 1),
isClosed: true,
sealStart: false,
sealEnd: false,
textureScale: new cga.Vector2(1 / roaddata[p].thickness, 1 / 2500)
})
// let geometry=
var roadMesh = new THREE.Mesh(geometry, roadmats[p]);
group.add(roadMesh);
group.position.set(-104.29041, 0, -303.55284);
}
roadGroup.add(group);
roadGroup.rotation.y = Math.PI / 2;
}
scene.add(roadGroup)
function initControls() {
const controls = new THREE.OrbitControls(camera, renderer.domElement);
// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
controls.autoRotateSpeed = 0.5;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
//controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true
return controls
}
const controls = initControls()
function animate() {
roadmats[0].map.offset.x += 0.003
roadmats[1].map.offset.x += 0.006
roadmats[2].map.offset.x += 0.009
controls.update()
renderer.render(scene,camera)
requestAnimationFrame(animate)
}
animate()
}});
function animate() {
controls.update()
renderer.render(scene, camera)
requestAnimationFrame(animate)
}
animate()
</script>
</body>
</html>