1
0
mirror of https://github.com/taigrr/skyline synced 2025-01-18 04:33:13 -08:00
skyline/js/contributions.js
2020-12-11 16:45:14 -05:00

302 lines
7.8 KiB
JavaScript

import * as THREE from "./three.module.js"
import { GLTFLoader } from './GLTFLoader.js'
import { OrbitControls } from './OrbitControls.js'
import { OBJExporter } from './OBJExporter.js'
import { GUI } from './dat.gui.module.js'
const BASE_LENGTH = 0.834
const BASE_WIDTH = 0.167
const BASE_HEIGHT = 0.05
const CUBE_SIZE = 0.0143
const MAX_HEIGHT = 0.14
const FACE_ANGLE = 104.79
let json = {}
let font = undefined
let fontSize = 0.025
let fontHeight = 0.00658 // Extrusion thickness
let nameMesh
let yearMesh
let camera, scene, renderer, barGroup
let bronzeMaterial
const gui = new GUI()
const params = {
username: 'jasonlong',
year: '2019'
}
let usernameController
let yearController
let exporter = new OBJExporter()
// Import JSON data
async function loadJSON(username, year) {
let url = `https://json-contributions.vercel.app/api/user?username=${username}&year=${year}`
let response = await fetch(url)
if (response.ok) {
json = await response.json()
addBars()
addText()
render()
} else {
alert("HTTP-Error: " + response.status)
}
}
usernameController = gui.add(params, 'username').name('Username')
usernameController.onChange(() => {
removeBars()
removeText()
loadJSON(params.username, params.year)
})
yearController = gui.add(params, 'year').name('Year')
yearController.onChange(() => {
removeBars()
removeText()
loadJSON(params.username, params.year)
})
const fixSideNormals = (geometry) => {
let triangle = new THREE.Triangle()
// "fix" side normals by removing z-component of normals for side faces
var triangleAreaHeuristics = 0.1 * (fontHeight * fontSize)
for (var i = 0; i < geometry.faces.length; i++) {
let face = geometry.faces[i]
if (face.materialIndex == 1) {
for (var j = 0; j < face.vertexNormals.length; j++) {
face.vertexNormals[ j ].z = 0
face.vertexNormals[ j ].normalize()
}
let va = geometry.vertices[ face.a ]
let vb = geometry.vertices[ face.b ]
let vc = geometry.vertices[ face.c ]
let s = triangle.set(va, vb, vc).getArea()
if (s > triangleAreaHeuristics) {
for (var j = 0; j < face.vertexNormals.length; j++) {
face.vertexNormals[ j ].copy(face.normal)
}
}
}
}
}
const createText = () => {
let nameGeo = new THREE.TextGeometry(params.username, {
font: font,
size: fontSize,
height: fontHeight
})
nameGeo.computeBoundingBox()
nameGeo.computeVertexNormals()
let yearGeo = new THREE.TextGeometry(params.year, {
font: font,
size: fontSize,
height: fontHeight
})
yearGeo.computeBoundingBox()
yearGeo.computeVertexNormals()
fixSideNormals(nameGeo)
fixSideNormals(yearGeo)
nameGeo = new THREE.BufferGeometry().fromGeometry(nameGeo)
nameMesh = new THREE.Mesh(nameGeo, bronzeMaterial)
nameMesh.position.x = -0.295
nameMesh.position.y = -0.075
nameMesh.position.z = -0.010
nameMesh.geometry.rotateX(FACE_ANGLE * Math.PI / 2)
nameMesh.geometry.rotateY(Math.PI * 2)
nameMesh.name = "name_mesh"
scene.add(nameMesh)
yearGeo = new THREE.BufferGeometry().fromGeometry(yearGeo)
yearMesh = new THREE.Mesh(yearGeo, bronzeMaterial)
yearMesh.position.x = 0.280
yearMesh.position.y = -0.075
yearMesh.position.z = -0.010
yearMesh.geometry.rotateX(FACE_ANGLE * Math.PI / 2)
yearMesh.geometry.rotateY(Math.PI * 2)
yearMesh.name = "year_mesh"
scene.add(yearMesh)
}
const render = () => {
renderer.render(scene, camera)
}
const init = () => {
// SCENE
scene = new THREE.Scene()
// CAMERA
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 100)
// RENDERER
renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.outputEncoding = THREE.sRGBEncoding
renderer.setClearColor(0xffffff, 1)
document.body.appendChild(renderer.domElement)
// MATERIALS
let phongMaterial = new THREE.MeshPhongMaterial({ color: 0x40c463, transparent: true, opacity: 0.2, side: THREE.DoubleSide })
bronzeMaterial = new THREE.MeshPhysicalMaterial({ color: 0x645f61, metalness: 1, clearcoat: 0.5, clearcoatRoughness: 0.5, side: THREE.DoubleSide })
// LIGHTS
const dLight1 = new THREE.DirectionalLight(0xebeb8c, 0.8)
dLight1.position.set(-2, -5, 5);
dLight1.target.position.set(0, 0, 0);
const dLight2 = new THREE.DirectionalLight(0xebeb8c, 0.8)
dLight2.position.set(2, -5, 5);
dLight2.target.position.set(0, 0, 0);
scene.add(dLight1)
scene.add(dLight2)
// LOAD REFERENCE MODEL
let loader = new GLTFLoader().setPath('../models/')
loader.load('ashtom-orig.glb', function (gltf) {
gltf.scene.traverse(function (child) {
if (child.isMesh) {
child.material = phongMaterial
child.material.depthWrite = !child.material.transparent
}
})
gltf.scene.rotation.x = Math.PI/2
gltf.scene.rotation.y = -Math.PI
// let worldAxis = new THREE.AxesHelper(2);
// scene.add(worldAxis)
render()
})
// BASE GEOMETRY
let baseLoader = new GLTFLoader().setPath('../models/')
baseLoader.load('base.glb', function (base) {
base.scene.traverse(function (child) {
if (child.isMesh) {
child.material = bronzeMaterial
child.material.depthWrite = !child.material.transparent
}
})
base.scene.rotation.x = -Math.PI/2
base.scene.rotation.z = -Math.PI
scene.add(base.scene)
})
const box = new THREE.Box3().setFromObject(scene)
const center = box.getCenter(new THREE.Vector3())
camera.lookAt(center)
camera.position.y = -0.4
camera.position.z = 0.3
let controls = new OrbitControls(camera, renderer.domElement)
controls.autoRotate = false
controls.screenSpacePanning = true
controls.addEventListener('change', render)
}
const addBars = () => {
// CONTRIBUTION BARS
barGroup = new THREE.Group()
let maxCount = json.max
let x = 0
let y = 0
json.contributions.forEach(week => {
y = (CUBE_SIZE * 7)
week.days.forEach(day => {
y -= CUBE_SIZE
let height = (MAX_HEIGHT / maxCount * day.count).toFixed(4)
let geometry = new THREE.BoxGeometry(CUBE_SIZE, CUBE_SIZE, height)
let cube = new THREE.Mesh(geometry, bronzeMaterial)
cube.position.x = x
cube.position.y = y
cube.position.z = BASE_HEIGHT / 2 + height / 2
barGroup.add(cube)
})
x += CUBE_SIZE
})
const groupBox = new THREE.Box3().setFromObject(barGroup)
const groupCenter = groupBox.getCenter(new THREE.Vector3())
barGroup.position.x -= groupCenter.x
barGroup.position.y -= groupCenter.y
scene.add(barGroup)
render()
}
const removeBars = () => {
scene.remove(barGroup)
barGroup = null
render()
}
const addText = () => {
let fontLoader = new THREE.FontLoader()
fontLoader.load('../fonts/helvetiker_regular.typeface.json', function (response) {
font = response
createText()
render()
})
}
const removeText = () => {
scene.remove(nameMesh)
scene.remove(yearMesh)
nameMesh = null
yearMesh = null
render()
}
//
// Event listeners
//
const onExport = () => {
const result = exporter.parse(mesh)
save(new Blob([ text ], { type: 'text/plain' }), filename)
}
const save = (blob, filename) => {
downloadLink.href = URL.createObjectURL(blob)
downloadLink.download = filename
downloadLink.click()
}
const onWindowResize = () => {
let canvasWidth = window.innerWidth
let canvasHeight = window.innerHeight
renderer.setSize(canvasWidth, canvasHeight)
camera.aspect = canvasWidth / canvasHeight
camera.updateProjectionMatrix()
render()
}
window.addEventListener('resize', onWindowResize, false)
///////////////////////////////////////////////////////////////////////
//
// ENTRY POINT
//
///////////////////////////////////////////////////////////////////////
init()
loadJSON(params.username, params.year)