使用Three.js为QQ用户生成3D头像阵列
东西其实比较简单,就是输出某一范围内QQ账号的3D头像
涉及的技术主要是Three.js的基本使用
而后通过腾讯的接口异步并发请求QQ用户头像,Canavs作图生成Texture应用在球体上
需要注意的是,必须修改Chrome启动参数允许访问跨域资源才可正常打开此页面
GitHub地址:https://github.com/gstoken/qq-cube
先上效果图
然后是主要代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="./css/bootstrap.css" />
<style>
html, body {
margin: 0px;
padding: 0px;
width: 100%;
height: 100%;
}
.page {
position: fixed;
width: 100%;
height: 100%;
background-color: white;
}
.top {
padding: 10px;
width: 100%;
background-color: #222;
color: white;
}
.content {
width: 100%;
height: 100%;
}
</style>
<title>QQ号顺序阵列查询</title>
</head>
<body>
<div class="page">
<div class="top">
<form class="form-inline">
<div class="form-group">
<label for="dstQQNum">QQ号:</label>
<input type="number" class="form-control" id="dstQQNum" value="1982775886" placeholder="目标QQ号" />
</div>
<div class="form-group">
<label for="querySize">查询数量:</label>
<input type="number" class="form-control" id="querySize" value="64" placeholder="查询个数" />
</div>
<div class="form-group">
<label for="taskNum">并发数量:</label>
<input type="number" class="form-control" id="taskNum" value="50" placeholder="并发数量" />
</div>
<div class="form-group">
<input id="standardQuery" type="button" class="btn btn-primary" value="查询" />
<input id="randomQuery" type="button" class="btn btn-primary" value="随机查查" />
<input id="clearQQ" type="button" class="btn btn-primary" value="清空QQ" />
</div>
</form>
</div>
<div class="content">
</div>
</div>
<script src="./js/jquery-3.3.1.js"></script>
<script src="./js/three.js"></script>
<script src="./js/OrbitControls.js"></script>
<script src="./js/Detector.js"></script>
<script src="./js/stats.min.js"></script>
<script type="x-shader/x-vertex" id="vertexShader">
varying vec3 vWorldPosition;
void main() {
vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
vWorldPosition = worldPosition.xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script type="x-shader/x-fragment" id="fragmentShader">
uniform vec3 topColor;
uniform vec3 bottomColor;
uniform float offset;
uniform float exponent;
varying vec3 vWorldPosition;
void main() {
float h = normalize( vWorldPosition + offset ).y;
gl_FragColor = vec4( mix( bottomColor, topColor, max( pow( max( h , 0.0), exponent ), 0.0 ) ), 1.0 );
}
</script> <script>
if (!Detector.webgl) Detector.addGetWebGLMessage(); //球体贴图宽
const ballTextureWidth = 256;
//球体贴图高
const ballTextureHeight = 128;
//头像尺寸
const headSize = 128;
//摄像机,场景,渲染器
let camera, scene, renderer;
let stats; let $area = $(".content"); let meshList = []; //绘制图像到canvas
function drawToCanvas (img, canvas) {
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, (ballTextureHeight - headSize) / 2, headSize, headSize);
ctx.drawImage(img, ballTextureWidth / 2, (ballTextureHeight - headSize) / 2, headSize, headSize);
} //根据QQ以及坐标位置信息生成球体对象
function createBallMesh (img) {
let canvas = document.createElement("canvas");
canvas.width = ballTextureWidth;
canvas.height = ballTextureHeight; let ctx = canvas.getContext("2d");
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, ballTextureWidth, ballTextureHeight);
drawToCanvas(img, canvas); let texture = new THREE.Texture(canvas);
//新建标准网孔材质
let ballMat = new THREE.MeshStandardMaterial( {
color: "white",
roughness: 0.4,
metalness: 0.4,
map: texture
});
texture.needsUpdate = true; let ballGeometry = new THREE.SphereGeometry(0.5, 32, 32);
let ballMesh = new THREE.Mesh(ballGeometry, ballMat);
ballMesh.rotation.y = Math.PI; texture = undefined;
canvas = undefined; return ballMesh;
} async function buildQQMeshList (qqNumList, concurrencyNum) {
let rtvList = [];
let getList = await getAllQQHead(qqNumList, concurrencyNum);
for (let i = 0; i < getList.length; ++i) {
let curQQInfo = getList[i];
let curQQNum = curQQInfo.qqNum;
let curMesh = createBallMesh(curQQInfo.headImg);
rtvList[i] = {
qqNum: curQQNum,
qqMesh: curMesh
};
}
return rtvList;
} async function draw (scene, list, concurrencyNum) {
clear();
meshList = await buildQQMeshList(list, concurrencyNum);
meshList.sort((a, b) => {
return a.qqNum - b.qqNum;
});
len = meshList.length;
let size = Math.ceil(Math.cbrt(len));
let i = 0;
for (let z = 0; z > -size; --z) {
for (let y = size - 1; y >= 0; --y) {
for (let x = 0; x < size; ++x) {
if (i < len) {
let ballMesh = meshList[i++].qqMesh;
ballMesh.position.set(x * 2, y * 2, z * 2);
scene.add(ballMesh);
}
else {
return;
}
}
}
}
} function clear () {
for (let i = 0; i < meshList.length; ++i) {
scene.remove(meshList[i].qqMesh);
}
} async function init(element) {
let areaWidth = $area.width();
let areaHeight = $area.height(); //初始化相机
camera = new THREE.PerspectiveCamera(30, areaWidth / areaHeight, 1, 5000);
camera.position.set(0, 0, 25); //初始化场景
scene = new THREE.Scene();
scene.background = new THREE.Color().setHSL(0.6, 0, 1);
scene.fog = new THREE.Fog(scene.background, 1, 5000); //添加半球光源
let = hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6);
hemiLight.color.setHSL(0.6, 1, 0.6);
hemiLight.groundColor.setHSL(0.095, 1, 0.75);
hemiLight.position.set(0, 50, 0);
scene.add(hemiLight);
//添加直接光源
let dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.color.setHSL(0.1, 1, 0.95);
dirLight.position.set(-1, 1.75, 1);
dirLight.position.multiplyScalar(30);
scene.add(dirLight); //环境
let groundGeo = new THREE.PlaneBufferGeometry(10000, 10000);
let groundMat = new THREE.MeshPhongMaterial({color: 0xffffff, specular: 0x050505});
groundMat.color.setHSL(0.095, 1, 0.75);
let ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -33;
scene.add(ground);
ground.receiveShadow = true; //天幕
var vertexShader = document.getElementById('vertexShader').textContent;
var fragmentShader = document.getElementById('fragmentShader').textContent;
var uniforms = {
topColor: {value: new THREE.Color(0x0077ff)},
bottomColor: {value: new THREE.Color(0xffffff)},
offset: {value: 33},
exponent: {value: 0.6}
};
uniforms.topColor.value.copy(hemiLight.color);
scene.fog.color.copy(uniforms.bottomColor.value);
var skyGeo = new THREE.SphereGeometry(4000, 32, 15);
var skyMat = new THREE.ShaderMaterial( { vertexShader: vertexShader, fragmentShader: fragmentShader, uniforms: uniforms, side: THREE.BackSide } );
var sky = new THREE.Mesh( skyGeo, skyMat );
scene.add(sky); //渲染器
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(areaWidth, areaHeight);
element.appendChild(renderer.domElement);
renderer.gammaInput = true;
renderer.gammaOutput = true;
renderer.shadowMap.enabled = true; //初始化轨道控制器
controls = new THREE.OrbitControls(camera, renderer.domElement); // stats = new Stats();
// element.appendChild(stats.dom); window.addEventListener('resize', onWindowResize, false);
} function onWindowResize() {
let width = $area.width();
let height = $area.height();
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
} function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
//stats.update();
} (async () => {
let element = $area[0];
await init(element);
animate();
})();
</script> <script>
//根据起始QQ,结束QQ生成QQ列表
function buildInputQQNumList (opQQNum, edQQNum) {
let len = edQQNum - opQQNum + 1;
let list = Array(len).fill(0).map((item, index) => {
return opQQNum + index;
});
return list;
}
//闭区间随机选数
function randomRange (op, ed) {
let len = ed - op + 1;
let offset = Math.floor(Math.random() * len);
return op + offset;
}
//随机生成模拟数据
function buildTestList (len) {
let randOpQQNum = randomRange(10000, 100000000);
let edQQNum = randOpQQNum + len - 1;
return buildInputQQNumList(randOpQQNum, edQQNum);
}
//对列表按索引分组,传入列表和分组个数
function batch (list, num) {
let batchList = [];
for (let i = 0; i < num; ++i) {
batchList[i] = list.filter((item, index) => {
if (index % num == i) {
return true;
}
else {
return false;
}
});
}
return batchList;
}
</script> <script>
//根据url获取Image对象,返回一个Promise
function getImage (url, timeout) {
timeout = timeout || 60000;
return new Promise((resolve, reject) => {
try {
let imgObj = new Image();
imgObj.src = url;
imgObj.crossOrigin = ""; let id = setTimeout(() => {
reject();
}, timeout); imgObj.onload = () => {
clearTimeout(id);
resolve(imgObj);
}; imgObj.onerror = () => {
reject();
};
}
catch (e) {
reject();
}
});
}
//获取访问QQ头像的接口url
function getQQHeadUrl (qqNum) {
//return "https://qlogo2.store.qq.com/qzone/" + qqNum + "/" + qqNum + "/100";
return "http://q2.qlogo.cn/headimg_dl?dst_uin=" + qqNum + "&spec=100";
}
//获取QQ头像
function getQQHead (qqNum) {
return getImage(getQQHeadUrl(qqNum));
}
//根据传入的QQ账号列表生成一个Promise异步任务获取对应QQ头像
function startGetQQHeadTask (qqNumList) {
return new Promise(async (resolve, reject) => {
let taskArray = [];
for (let i = 0; i < qqNumList.length; ++i) {
let curQQNum = qqNumList[i];
let curQQHeadImg;
try {
curQQHeadImg = await getQQHead(curQQNum);
}
catch (e) {
curQQHeadImg = null;
}
taskArray[i] = {
qqNum: curQQNum,
headImg: curQQHeadImg
};
}
resolve(taskArray);
});
}
//根据QQ账号列表获取所有头像
function getAllQQHead (qqNumList, concurrencyNum) {
return new Promise((resolve, reject) => {
let rtvList = [];
let batchList = batch(qqNumList, concurrencyNum);
let count = 0;
for (let i = 0; i < batchList.length; ++i) {
let dstTaskList = batchList[i];
startGetQQHeadTask(dstTaskList).then((list) => {
rtvList = rtvList.concat(list);
count++;
if (count >= concurrencyNum) {
resolve(rtvList);
}
});
}
});
}
</script> <script>
$(function () {
$("#standardQuery").click(async () => {
let dstQQNum = Number(document.getElementById("dstQQNum").value);
let querySize = Number(document.getElementById("querySize").value);
if (querySize < 1) {
alert("查询个数输入错误");
return;
}
let taskNum = Number(document.getElementById("taskNum").value);
if (taskNum < 1) {
alert("并发数量输入错误");
return;
}
let list = buildInputQQNumList(dstQQNum, dstQQNum + querySize - 1);
await draw(scene, list, taskNum);
});
$("#randomQuery").click(async () => {
let taskNum = Number(document.getElementById("taskNum").value);
let list = buildTestList(4 * 4 * 4);
await draw(scene, list, taskNum);
});
$("#clearQQ").click(() => {
clear();
});
});
</script>
</body>
</html>
使用Three.js为QQ用户生成3D头像阵列的更多相关文章
- JS获得QQ号码的昵称,头像,生日
这篇文章主要介绍了JS获得QQ号码的昵称,头像,生日的简单实例,有需要的朋友可以参考一下 http://r.qzone.qq.com/cgi-bin/user/cgi_personal_card?ui ...
- 我的世界 ParaCraft 结合开源地图 OpenStreetMap 生成3D校园的方法简介
我的世界ParaCraft结合开源地图OpenStreetMap生成3D校园的方法简介 版本1.0 日期2019.2.3 作者Ray (82735589@qq.com) www.TimeGIS.com ...
- Django商城项目笔记No.12用户部分-QQ登录2获取QQ用户openid
Django商城项目笔记No.12用户部分-QQ登录2获取QQ用户openid 上一步获取QQ登录网址之后,测试登录之后本该跳转到这个界面 但是报错了: 新建oauth_callback.html & ...
- webgl(three.js)实现室内三维定位,3D定位,3D楼宇bim、实时定位三维可视化解决方案——第十四课(定位升级版)
序: 还是要抽出时间看书的,迷上了豆豆的作品,最近在看<天幕红尘>,书中主人公的人生价值观以及修为都是让我惊为叹止.很想成为那样的人,但是再看看自己每天干的事,与时间的支配情况,真是十分的 ...
- 使用腾讯开发平台获取QQ用户数据资料
<今天是七夕:祝大家七夕嗨皮,前可么么哒,后可啪啪啪> Tips:本篇博客将教你如何使用腾讯开发平台获取QQ用户资料 ----------------------------------- ...
- Space.js – HTML 驱动的页面 3D 滚动效果
为了让我们的信息能够有效地沟通,我们需要创建用户和我们的媒体之间的强有力的联系.今天我们就来探讨在网络上呈现故事的新方法,并为此创造了一个开源和免费使用的 JavaScript 库称为 space.j ...
- 我从腾讯那“偷了”3000万QQ用户数据,出了份很有趣的独家报告!
声明: 1.目前程序已停止运行!QQ空间也已升级访问安全机制. 2.本“分析”数据源自部分用户的公开信息,并未触及隐私内容,广大网友无需担心. 3.QQ空间会不定期发布大数据分析报告,感兴趣的朋友关注 ...
- JS仿QQ空间鼠标停在长图片时候图片自动上下滚动效果
JS仿QQ空间鼠标停在长图片时候图片自动上下滚动效果 今天是2014年第一篇博客是关于类似于我们的qq空间长图片展示效果,因为一张很长的图片不可能全部把他展示出来,所以外层用了一个容器给他一个高度,超 ...
- Three.js 实现2022冬奥主题3D趣味页面 🐼
背景 迎冬奥,一起向未来!2022冬奥会马上就要开始了,本文使用 Three.js + React 技术栈,实现冬日和奥运元素,制作了一个充满趣味和纪念意义的冬奥主题 3D 页面.本文涉及到的知识点主 ...
随机推荐
- 任务超时退出的方法 C#
超出时间方法退出.防止卡住. 方法: private static bool ImportTaskTimeout(Action method, int hours) { try { var task ...
- 利用SVD-推荐未尝过的菜肴
推荐未尝过的菜肴-基于物品相似度的推荐 推荐系统的工作过程:给定一个用户,系统会为此用户返回N个最好的推荐菜 1. 寻找用户没有评级的菜肴,即在用户-物品矩阵中的0值 2. 在用户没有评级的所有物品中 ...
- python 给对象绑定属性和方法和__slots__的使用
# 以c语言为主是静态语言,运行之前先编译,在运行的过程中不允许编辑代码# 在运行的过程中,可以改变,可以添加属性,就是属于动态语言(python) # python动态的添加属性以及方法class ...
- Git reset与checkout的区别
reset: 将暂存区的文件回撤到工作区,文件内容不会有任何变化 checkout: 将工作区文件恢复到上一次commit时的内容,将会丢失修改了但未加入暂存区的内容
- IT设备服务监控的方法论
有方法论提导,在技战术方面才不会偏离目录. 使用服务级别作为关键语,召示着承诺和责任. https://www.circonus.com/2018/06/comprehensive-container ...
- WARN conf.FlumeConfiguration: Could not configure sink sink1 due to: No channel configured for sink: sink1 org.apache.flume.conf.ConfigurationException: No channel configured for sink: sink1
1.错误如下所示,启动flume采集文件到hdfs案例的时候,出现如下所示的错误: 大概是说No channel configured for sink,所以应该是sink哪里配置出现了错误,百度了一 ...
- Windows Azure 部署 Windows 8 虚拟机
基本步骤其实很简单,主要有: 本地部署虚拟机 将虚拟机VHD上传至Azure 在Azure上根据VHD生成映像 利用映像生成虚拟机 下面我们开始: 1,本地部署虚拟机 首先我们需要在本地用 Hyper ...
- javascript获取时间戳
时间戳: 时间戳是自 1970 年 1 月 1 日(00:00:00 GMT)以来的秒数.它也被称为 Unix 时间戳(Unix Timestamp). JavaScript 获取当前时间戳: < ...
- python全栈开发day72-django之Form组件
一.ajax 1. 复习JSON 1. JSON是什么? 一种数据格式,和语言无关的数据格式. 2. Python里面转换 1. Python对象 --> 字符串 import json 字符串 ...
- 分布式系统的CAP理论
一.CAP理论概述 一个分布式系统最多只能同时满足一致性(Consistency).可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项. 二.CAP ...