突然发现了一个特别有意思的网站,记录一下:https://zzz.dog 。
免费开源!使用简单、超轻量的 javacript 3D 模型引擎 - Zdog.js
Zdog.js (下称Zdog)是一款基于和 SVG 的 JavaScript 3D 引擎,通过简单的 api 可以让我们在 Web 页面上设计和渲染简单的 3D 模型。Zdog 是一个伪 3D 引擎。其几何形状存在于3D空间中,但被渲染为平面形状。这使得 Zdog 很特别。
Node包下载:
npm install zdog
Loading.vue
<template>
<div>
<div class="continer">
<canvas class="illo"></canvas>
</div>
</div>
</template>
<script>
import animate from '../castle.js'
export default {
name: 'Loading',
mounted() {
animate()
}
}
</script>
<style scoped>
.continer{
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.illo {
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 220px !important;
width: 220px !important;
margin: auto;
cursor: move;
z-index: 6;
}
</style>
castle.js
// -------------------------- demo -------------------------- //
import Zdog from 'zdog'
export default function() {
// Made with Zdog
// colors
const red = '#F44'
const navy = '#247'
const blue = '#5AE'
const gold = '#FB3'
const white = 'white'
// -------------------------- makeBuilding -------------------------- //
function makeBuilding(options) {
const wallX = options.width / 2
const wallY = options.height
const wallZ = options.depth / 2
// collect walls
const building = {};
// south/noth walls
[true, false].forEach(function(isSouth) {
const wallTZ = isSouth ? -wallZ : wallZ
const wallGroup = new Zdog.Group({
addTo: options.addTo,
translate: { z: -wallTZ }
})
let wallPath = [
{ x: -wallX, y: -wallY }
]
if (options.gable == 'ns') {
wallPath.push({ x: 0, y: -wallY - wallX })
}
wallPath = wallPath.concat([
{ x: wallX, y: -wallY },
{ x: wallX, y: 0 },
{ x: -wallX, y: 0 }
])
// wall
new Zdog.Shape({
path: wallPath,
addTo: wallGroup,
color: isSouth ? red : gold
})
const windowColor = isSouth ? navy : red
const windowProperty = isSouth ? 'southWindows' : 'northWindows'
handleWindows(options, windowProperty, wallGroup, windowColor)
const wallProperty = isSouth ? 'southWall' : 'northWall'
building[wallProperty] = wallGroup
});
// east/west wall
[true, false].forEach(function(isWest) {
const wallGroup = new Zdog.Group({
addTo: options.addTo,
translate: { x: isWest ? -wallX : wallX },
rotate: { y: TAU / 4 }
})
let wallPath = [
{ x: -wallZ, y: -wallY }
]
if (options.gable == 'ew') {
wallPath.push({ x: 0, y: -wallY - wallZ })
}
wallPath = wallPath.concat([
{ x: wallZ, y: -wallY },
{ x: wallZ, y: 0 },
{ x: -wallZ, y: 0 }
])
// wall
new Zdog.Shape({
path: wallPath,
addTo: wallGroup,
color: isWest ? blue : white
})
const windowColor = isWest ? navy : blue
const windowProperty = isWest ? 'westWindows' : 'eastWindows'
handleWindows(options, windowProperty, wallGroup, windowColor)
const wallProperty = isWest ? 'westWall' : 'eastWall'
building[wallProperty] = wallGroup
})
const roofMakers = {
ns: function() {
const y0 = -wallY - wallX
const roofPanel = new Zdog.Shape({
path: [
{ x: 0, y: y0, z: wallZ },
{ x: 0, y: y0, z: -wallZ },
{ x: wallX, y: -wallY, z: -wallZ },
{ x: wallX, y: -wallY, z: wallZ }
],
addTo: options.addTo,
color: gold
})
roofPanel.copy({
scale: { x: -1 },
color: navy
})
},
ew: function() {
const y0 = -wallY - wallZ
const xA = options.isChurch ? -wallX + 8 : -wallX
const roofPanel = new Zdog.Shape({
path: [
{ z: 0, y: y0, x: xA },
{ z: 0, y: y0, x: wallX },
{ z: -wallZ, y: -wallY, x: wallX },
{ z: -wallZ, y: -wallY, x: xA }
],
addTo: options.addTo,
color: red
})
roofPanel.copy({
path: [
{ z: 0, y: y0, x: -wallX },
{ z: 0, y: y0, x: wallX },
{ z: -wallZ, y: -wallY, x: wallX },
{ z: -wallZ, y: -wallY, x: -wallX }
],
scale: { z: -1 },
color: navy
})
}
}
const roofMaker = roofMakers[options.gable]
if (roofMaker) {
roofMaker()
}
return building
}
function handleWindows(options, windowProperty, wallGroup, color) {
const windowOption = options[windowProperty]
if (!windowOption) {
return
}
const columns = windowOption[0]
const rows = windowOption[1]
// let windowPaths = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < columns; col++) {
const x = (col - (columns - 1) / 2) * 6
const y = -options.height + (row + 0.75) * 8
const windowPath = [
{ x: x + -1, y: y + -2 },
{ x: x + 1, y: y + -2 },
{ x: x + 1, y: y + 2 },
{ x: x + -1, y: y + 2 }
]
new Zdog.Shape({
path: windowPath,
addTo: wallGroup,
color: color
})
}
}
}
// -------------------------- lilPyramid -------------------------- //
function lilPyramid(options) {
const anchor = new Zdog.Anchor({
addTo: options.addTo,
translate: options.translate
})
const panel = new Zdog.Shape({
path: [
{ x: 0, y: -3, z: 0 },
{ x: 3, y: 0, z: 0 },
{ x: 0, y: 0, z: 3 }
],
addTo: anchor,
color: red
})
panel.copy({
rotate: { y: TAU / 4 },
color: red
})
panel.copy({
rotate: { y: TAU / 2 },
color: navy
})
panel.copy({
rotate: { y: TAU * 3 / 4 },
color: navy
})
}
function hedge(options) {
const anchor = new Zdog.Anchor({
addTo: options.addTo,
translate: options.translate
})
const ball = new Zdog.Shape({
path: [{ y: 0 }, { y: -1 }],
addTo: anchor,
translate: { y: -2.5 },
stroke: 5,
color: options.color || navy
})
ball.copy({
stroke: 4,
translate: { y: -5 }
})
ball.copy({
stroke: 2.5,
translate: { y: -7.5 }
})
}
// -------------------------- demo -------------------------- //
const illoElem = document.querySelector('.illo')
const w = 160
const h = 160
const minWindowSize = Math.min(window.innerWidth, window.innerHeight)
const zoom = Math.min(6, Math.floor(minWindowSize / w))
illoElem.setAttribute('width', w * zoom)
illoElem.setAttribute('height', h * zoom)
let isSpinning = true
const TAU = Zdog.TAU
const illo = new Zdog.Illustration({
element: illoElem,
zoom: zoom,
rotate: { y: TAU / 8 },
dragRotate: true,
onDragStart: function() {
isSpinning = false
}
});
// default to flat, filled shapes
[Zdog.Shape, Zdog.Rect, Zdog.Ellipse].forEach(function(ItemClass) {
ItemClass.defaults.fill = true
ItemClass.defaults.stroke = false
})
// -- illustration shapes --- //
const quarterView = 1 / Math.sin(TAU / 8)
// anchor
const town = new Zdog.Group({
addTo: illo,
translate: { y: 36 },
scale: { x: quarterView, z: quarterView },
updateSort: true
})
// ----- front building ----- //
const frontAnchor = new Zdog.Anchor({
addTo: town,
translate: { x: 16, y: -4, z: 20 }
})
const frontBuilding = makeBuilding({
width: 22,
depth: 16,
height: 20,
addTo: frontAnchor,
gable: 'ew',
southWindows: [3, 1],
eastWindows: [2, 2],
westWindows: [2, 2],
northWindows: [3, 2]
})
// east gable dot
const gableDot = new Zdog.Ellipse({
diameter: 2,
addTo: frontBuilding.eastWall,
color: blue,
translate: { y: -20 }
})
// west gable dot
gableDot.copy({
addTo: frontBuilding.westWall,
color: navy
})
// south doors
const door = new Zdog.Shape({
path: [
{ x: -2.5, y: 0 },
{ x: -2.5, y: -5.5 },
{
arc: [
{ x: -2.5, y: -8 },
{ x: 0, y: -8 }
]
},
{
arc: [
{ x: 2.5, y: -8 },
{ x: 2.5, y: -5.5 }
]
},
{ x: 2.5, y: 0 }
],
addTo: frontBuilding.southWall,
translate: { x: -4.5 },
color: navy
})
door.copy({
translate: { x: 4.5 }
});
[-1, 1].forEach(function(zSide) {
const frontGableGroup = new Zdog.Group({
addTo: frontAnchor,
translate: { y: -20, z: -8 * zSide }
})
// front building gable
new Zdog.Shape({
path: [
{ x: 0, y: -6 },
{ x: -6, y: 0 },
{ x: 6, y: 0 }
],
addTo: frontGableGroup,
translate: { y: 1 },
color: zSide == -1 ? red : gold
})
gableDot.copy({
addTo: frontGableGroup,
translate: { y: -2 },
color: zSide == -1 ? navy : red
})
const frontGableSide = new Zdog.Shape({
path: [
{ x: 0, y: 0, z: 0 },
{ x: 5, y: 5, z: 0 },
{ x: 0, y: 0, z: 5 * zSide }
],
addTo: frontAnchor,
translate: { y: -25, z: -8 * zSide },
color: gold
})
frontGableSide.copy({
scale: { x: -1 },
color: navy
})
})
// ----- left building ----- //
const leftAnchor = new Zdog.Anchor({
addTo: town,
translate: { x: -13, y: -10, z: 23 }
})
const leftBuilding = makeBuilding({
width: 16,
depth: 22,
height: 20,
addTo: leftAnchor,
gable: 'ns',
southWindows: [2, 2],
eastWindows: [3, 2],
westWindows: [3, 1],
northWindows: [2, 2]
})
door.copy({
addTo: leftBuilding.westWall,
translate: { x: -4.5 }
})
door.copy({
addTo: leftBuilding.westWall,
translate: { x: 4.5 }
})
// ----- cupola ----- //
const cupolaNSPanel = new Zdog.Shape({
path: [
{ x: -1, y: 0 },
{ x: 3, y: 0 },
{ x: 3, y: 9 },
{ x: -1, y: 5 },
// HACK add point to sort in front of roof
{ move: { x: 8, z: 4 } }
],
addTo: leftAnchor,
translate: { y: -34, z: 3 },
color: red
})
cupolaNSPanel.copy({
scale: { x: -1 }
})
cupolaNSPanel.copy({
scale: { z: -1 },
translate: { y: -34, z: -3 },
color: gold
})
cupolaNSPanel.copy({
translate: { y: -34, z: -3 },
scale: { x: -1, z: -1 },
color: gold
});
[-1, 1].forEach(function(xSide) {
const group = new Zdog.Group({
addTo: leftAnchor,
translate: { y: -34, x: 3 * xSide }
})
// ew panel
new Zdog.Shape({
path: [
{ z: 3, y: 0 },
{ z: 0, y: -3 },
{ z: -3, y: 0 },
{ z: -3, y: 9 },
{ z: 3, y: 9 },
// HACK add point to sort in front of roof
{ move: { x: 16 * xSide }}
],
addTo: group,
color: xSide == -1 ? blue : white
})
gableDot.copy({
addTo: group,
translate: { y: 3 },
rotate: { y: TAU / 4 },
color: xSide == -1 ? navy : blue
})
})
// cupola roof panel
const cupolaRoofPanel = new Zdog.Shape({
path: [
{ x: -3, y: -3, z: 0 },
{ x: 3, y: -3, z: 0 },
{ x: 3, y: 0, z: 3 },
{ x: -3, y: 0, z: 3 }
],
addTo: leftAnchor,
translate: { y: -34 },
color: navy
})
cupolaRoofPanel.copy({
scale: { z: -1 },
color: red
})
// ----- left building slopes ----- //
// east slope
const leftEWSlope = new Zdog.Shape({
path: [
{ x: 0, y: 0, z: 11 },
{ x: 0, y: 0, z: -11 },
{ x: 6, y: 6, z: -11 },
{ x: 6, y: 6, z: 11 }
],
addTo: leftAnchor,
translate: { x: 8 },
color: gold
})
// west slope
leftEWSlope.copy({
scale: { x: -1 },
translate: { x: -8 },
color: gold
})
// south slope
new Zdog.Shape({
path: [
{ z: 0, y: 0, x: -8 },
{ z: 0, y: 0, x: 8 },
{ z: 6, y: 6, x: 8 },
{ z: 6, y: 6, x: -8 }
],
addTo: leftAnchor,
translate: { z: 11 },
color: navy
})
// south east corner
const leftCorner = new Zdog.Shape({
path: [
{ x: 0, y: 0, z: 0 },
{ x: 6, y: 6, z: 0 },
{ x: 0, y: 6, z: 6 }
],
addTo: leftAnchor,
translate: { x: 8, z: 11 },
color: red
})
// south west corner
leftCorner.copy({
scale: { x: -1 },
translate: { x: -8, z: 11 },
color: blue
})
// ----- back tower ----- //
const towerAnchor = new Zdog.Anchor({
addTo: town,
translate: { x: -13, y: -24, z: -4 }
})
const tower = makeBuilding({
width: 16,
depth: 16,
height: 28,
addTo: towerAnchor,
gable: 'ns',
southWindows: [2, 3],
eastWindows: [2, 2],
westWindows: [2, 3],
northWindows: [2, 3]
})
door.copy({
addTo: tower.eastWall,
translate: { x: 0 },
color: blue
})
gableDot.copy({
addTo: tower.southWall,
translate: { y: -29 },
color: navy
})
gableDot.copy({
addTo: tower.northWall,
translate: { y: -29 },
color: red
})
const towerChimney = new Zdog.Shape({
addTo: towerAnchor,
path: [{ y: 0 }, { y: 4 }],
translate: { x: -2, y: -37, z: 1 },
stroke: 2,
color: navy
})
towerChimney.copy({
translate: { x: -2, y: -37, z: -3 }
})
// plume
new Zdog.Shape({
path: [
{ x: -3, y: 1 },
{
arc: [
{ x: -3, y: -1 },
{ x: -1, y: -1 }
]
},
{ x: 3, y: -1 },
{
arc: [
{ x: 3, y: 1 },
{ x: 1, y: 1 }
]
}
],
addTo: towerAnchor,
translate: { x: -2, y: -42, z: -6 },
rotate: { y: -TAU / 4 },
stroke: 2,
color: blue
})
// ----- tower slopes ----- //
// big east slope
const towerEWSlope = new Zdog.Shape({
path: [
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 0, z: -1 },
{ x: 1, y: 1, z: -1 },
{ x: 1, y: 1, z: 1 }
],
addTo: towerAnchor,
translate: { x: 8 },
// size by scaling
scale: { x: 20, y: 20, z: 8 },
color: gold
})
// south slope down to left building
const towerNSSLope = new Zdog.Shape({
path: [
{ z: 0, y: 0, x: 1 },
{ z: 0, y: 0, x: -1 },
{ z: 1, y: 1, x: -1 },
{ z: 1, y: 1, x: 1 }
],
addTo: towerAnchor,
translate: { z: 8 },
scale: { x: 8, y: 14, z: 8 },
color: navy
})
// south east corner
new Zdog.Shape({
path: [
{ x: 0, y: 0, z: 0 },
{ x: 20, y: 20, z: 0 },
{ x: 6, y: 20, z: 8 },
{ x: 0, y: 14, z: 8 }
],
addTo: towerAnchor,
translate: { x: 8, z: 8 },
color: red
})
// north slope
towerNSSLope.copy({
translate: { z: -8 },
scale: { x: 8, y: 20, z: -7 },
color: gold
})
// north east corner
new Zdog.Shape({
path: [
{ x: 0, y: 0, z: 0 },
{ x: 20, y: 20, z: 0 },
{ x: 0, y: 20, z: -7 }
],
addTo: towerAnchor,
translate: { x: 8, z: -8 },
color: gold
})
// west slope
towerEWSlope.copy({
scale: { x: -12, y: 20, z: 8 },
translate: { x: -8 },
color: gold
})
// north west corner
new Zdog.Shape({
path: [
{ x: 0, y: 0, z: 0 },
{ x: -12, y: 20, z: 0 },
{ x: 0, y: 20, z: -7 }
],
addTo: towerAnchor,
translate: { x: -8, z: -8 },
color: red
})
// south west corner back to left building
new Zdog.Shape({
path: [
{ x: 0, y: 0, z: 0 },
{ x: -12, y: 20, z: 0 },
{ x: -6, y: 20, z: 8 },
{ x: 0, y: 14, z: 8 }
],
addTo: towerAnchor,
translate: { x: -8, z: 8 },
color: blue
})
// ----- church ----- //
const churchAnchor = new Zdog.Anchor({
addTo: town,
translate: { x: -5, y: -4, z: -27 }
})
const church = makeBuilding({
isChurch: true, // special flag for roof
width: 22,
depth: 16,
height: 28,
addTo: churchAnchor,
gable: 'ew',
southWindows: [3, 2],
eastWindows: [2, 2],
northWindows: [3, 2]
})
door.copy({
addTo: church.westWall,
translate: { x: -3.5 }
})
door.copy({
addTo: church.westWall,
translate: { x: 3.5 }
})
// big circle window
new Zdog.Ellipse({
diameter: 8,
addTo: church.westWall,
translate: { y: -22 },
color: navy
});
// ----- bell tower ----- //
(function() {
const bellTowerAnchor = new Zdog.Anchor({
addTo: churchAnchor,
translate: { x: -7, y: -36, z: -4 }
})
// tower ledge
new Zdog.Rect({
width: 8,
height: 8,
addTo: bellTowerAnchor,
translate: { y: -12 },
rotate: { x: TAU / 4 },
color: navy
})
const wallColors = [red, white, gold, blue]
const accentColors = [navy, blue, red, navy]
const roofColors = [navy, gold, red, navy]
for (let i = 0; i < 4; i++) {
const wallAnchor = new Zdog.Anchor({
addTo: bellTowerAnchor,
rotate: { y: TAU / 4 * -i }
})
const bottomWallGroup = new Zdog.Group({
addTo: wallAnchor,
translate: { z: 4 }
})
const wallColor = wallColors[i]
const accentColor = accentColors[i]
const roofColor = roofColors[i]
// bottom wall
new Zdog.Rect({
width: 8,
height: 12,
addTo: bottomWallGroup,
translate: { y: -6 },
color: wallColor
})
// circle cut-out
new Zdog.Ellipse({
diameter: 4,
addTo: bottomWallGroup,
translate: { y: -4 },
color: accentColor
})
// top stripe
new Zdog.Rect({
width: 8,
height: 2,
addTo: bottomWallGroup,
translate: { y: -9 },
color: accentColor
})
const topWallGroup = new Zdog.Group({
addTo: wallAnchor,
translate: { y: -12, z: 3 }
})
// top wall
new Zdog.Rect({
width: 6,
height: 7,
addTo: topWallGroup,
translate: { y: -3.5 },
color: wallColor
})
// top window
new Zdog.Rect({
width: 2,
height: 5,
addTo: topWallGroup,
translate: { y: -2.5 },
color: accentColor
})
// roof
new Zdog.Shape({
path: [
{ x: 0, y: 0, z: 0 },
{ x: -3, y: 6, z: 3 },
{ x: 3, y: 6, z: 3 }
],
addTo: wallAnchor,
translate: { y: -25 },
color: roofColor
})
}
// roof connectors
// south, white side
new Zdog.Shape({
path: [
{ z: 4, y: 0 },
{ z: -4, y: -1 },
{ z: -4, y: 8 }
],
addTo: bellTowerAnchor,
translate: { x: 4 },
color: white
})
// east gold side
const connector = new Zdog.Rect({
width: 8,
height: 10,
addTo: bellTowerAnchor,
translate: { z: -4, y: 4 },
color: gold
})
// north blue side
connector.copy({
translate: { x: -4, y: 4 },
rotate: { y: TAU / 4 },
color: blue
})
})()
// ----- hill ----- //
new Zdog.Shape({
path: [
{ x: 0, y: 2 },
{ x: 10, y: 2 },
{
bezier: [
{ x: 14, y: 2 },
{ x: 20, y: 10 },
{ x: 24, y: 10 }
]
},
{ x: 30, y: 10 },
{
arc: [
{ x: 34, y: 10 },
{ x: 34, y: 14 }
]
},
// bring it back into hill
{ x: 14, y: 14, z: 0 }
],
addTo: town,
translate: { x: -6, y: -20, z: -12 },
stroke: 4,
color: gold
})
// ----- lil pyramids ----- //
// front in front of left building
lilPyramid({
addTo: town,
translate: { x: 6, z: 35, y: -4 }
})
// behind left building
lilPyramid({
addTo: town,
translate: { x: -34, z: 20, y: -4 }
})
// front right
lilPyramid({
addTo: town,
translate: { x: 35, z: 8, y: -4 }
})
lilPyramid({
addTo: town,
translate: { x: 31, z: -2, y: -4 }
})
// in front of church
lilPyramid({
addTo: town,
translate: { x: 22, z: -28, y: -4 }
})
// ----- hedges ----- //
// to right of front building
hedge({
addTo: town,
translate: { x: 24, y: -4, z: 4 }
})
// right of church
hedge({
addTo: town,
translate: { x: -4, y: -4, z: -42 }
})
// in between tower & church
hedge({
addTo: town,
translate: { x: -30, y: -4, z: -18 }
// color: gold,
})
hedge({
addTo: town,
translate: { x: 9, y: -4, z: -17 }
// color: gold,
})
// ----- sun ----- //
new Zdog.Shape({
addTo: town,
translate: { x: -6, y: -52, z: -42 },
stroke: 6,
color: gold
})
// ----- sky particles ----- //
// dot above left building
const skyDot = new Zdog.Shape({
translate: { x: -3, y: -48, z: 42 },
addTo: town,
stroke: 2,
color: white
})
// in front of church
skyDot.copy({
translate: { x: 30, y: -28, z: -28 }
})
const skyDiamond = new Zdog.Shape({
path: [
{ x: 0, y: -1 },
{ x: 1, y: 0 },
{ x: 0, y: 1 },
{ x: -1, y: 0 }
],
addTo: town,
translate: { x: -27, y: -45, z: 29 },
scale: 0.75,
stroke: 0.5,
color: white
})
skyDiamond.copy({
rotate: { y: TAU / 4 }
})
const skyDiamond2 = skyDiamond.copy({
translate: { x: 8, y: -34, z: -42 }
})
skyDiamond2.copy({
rotate: { y: TAU / 4 }
})
const skyStar = new Zdog.Shape({
path: [
{ x: 0, y: -1 },
{
arc: [
{ x: 0, y: 0 },
{ x: 1, y: 0 }
]
},
{
arc: [
{ x: 0, y: 0 },
{ x: 0, y: 1 }
]
},
{
arc: [
{ x: 0, y: 0 },
{ x: -1, y: 0 }
]
},
{
arc: [
{ x: 0, y: 0 },
{ x: 0, y: -1 }
]
}
],
addTo: town,
translate: { x: -39, y: -51, z: 12 },
scale: 1.5,
stroke: 1,
color: white
})
skyStar.copy({
rotate: { y: TAU / 4 }
})
// up front
const skyStar2 = skyStar.copy({
translate: { x: 29, y: -42, z: 30 },
color: white
})
skyStar2.copy({
rotate: { y: TAU / 4 }
})
// ----- clouds ----- //
const cloud = new Zdog.Ellipse({
addTo: town,
diameter: 3,
quarters: 2,
translate: { x: -30, y: -56, z: 10 },
rotate: { y: TAU / 4, z: -TAU / 4 },
stroke: 2,
closed: true,
color: white
})
cloud.copy({
translate: { x: -30, y: -57, z: 6 }
})
cloud.copy({
translate: { x: -30, y: -56, z: 2 }
})
// line underneath
new Zdog.Shape({
addTo: town,
path: [{ x: -1 }, { x: 1 }],
translate: { x: -30, y: -56, z: 6 },
scale: { x: 2 },
rotate: { y: TAU / 4 },
stroke: 2,
color: white
})
// ----- flat earth ----- //
const flatEarth = new Zdog.Ellipse({
diameter: 128,
addTo: illo,
translate: town.translate,
rotate: { x: TAU / 4 },
stroke: 8,
color: navy
})
// ----- sky ----- //
const sky = new Zdog.Group({
addTo: illo,
translate: town.translate
// translate: { y: 2 },
});
(function() {
const topYs = [
-64, -64, -52, -52,
-44, -44, -36, -36,
-44, -44, -52, -52,
-60, -60, -52, -52
]
const bottomYs = [
-24, -24, -16, -16,
-8, -8, -0, -0,
-8, -8, -16, -16,
-24, -24, -32, -32
]
const radius = 64
const skyPanelCount = topYs.length
const angle = TAU / skyPanelCount
const panelWidth = Math.tan(angle / 2) * radius * 2
for (let i = 0; i < skyPanelCount; i++) {
const nextI = (i + 1) % skyPanelCount
const topYA = topYs[i]
const topYB = topYs[nextI]
const bottomYA = bottomYs[i]
const bottomYB = bottomYs[nextI]
const panelAnchor = new Zdog.Anchor({
addTo: sky,
rotate: { y: angle * i - TAU / 4 },
translate: { y: 1 }
})
new Zdog.Shape({
path: [
{ x: -panelWidth / 2, y: topYA },
{
bezier: [
{ x: 0, y: topYA },
{ x: 0, y: topYB },
{ x: panelWidth / 2, y: topYB }
]
},
{ x: panelWidth / 2, y: bottomYB },
{
bezier: [
{ x: 0, y: bottomYB },
{ x: 0, y: bottomYA },
{ x: -panelWidth / 2, y: bottomYA }
]
}
],
addTo: panelAnchor,
translate: { z: -radius },
color: blue,
stroke: 1,
backface: false
})
}
})()
// -- animate --- //
let t = 0
const tSpeed = 1 / 120
let then = new Date() - 1 / 60
function animate() {
update()
render()
requestAnimationFrame(animate)
}
animate()
// -- update -- //
function update() {
const now = new Date()
const delta = now - then
if (isSpinning) {
t += tSpeed * delta / 60
const theta = Zdog.easeInOut(t % 1) * TAU
const rev = 1
const spin = -theta * rev + TAU / 8
const extraRotation = TAU * rev * Math.floor((t % 4))
illo.rotate.y = spin - extraRotation
const everyOtherCycle = t % 2 < 1
illo.rotate.x = everyOtherCycle ? 0 : (Math.cos(theta) * -0.5 + 0.5) * TAU * -1 / 8
}
illo.normalizeRotate()
// rotate
illo.updateGraph()
then = now
}
// -- render -- //
function render() {
const ctx = illo.ctx
illo.prerenderCanvas()
// render shapes
const isCameraXUp = illo.rotate.x < 0 || illo.rotate.x > TAU / 2
sky.renderGraphCanvas(ctx)
// HACK sort flat earth & town shapes manually
if (isCameraXUp) {
flatEarth.renderGraphCanvas(ctx)
}
town.renderGraphCanvas(ctx)
if (!isCameraXUp) {
flatEarth.renderGraphCanvas(ctx)
}
illo.postrenderCanvas()
ctx.restore()
}
}