convert all indents to tabs
This commit is contained in:
+240
-240
@@ -12,10 +12,10 @@ const renderPass = new THREE.RenderPass(scene, camera)
|
||||
composer.addPass(renderPass)
|
||||
|
||||
const renderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
|
||||
minFilter: THREE.NearestFilter,
|
||||
magFilter: THREE.NearestFilter,
|
||||
format: THREE.RGBAFormat,
|
||||
type: THREE.UNSIGNED_BYTE
|
||||
minFilter: THREE.NearestFilter,
|
||||
magFilter: THREE.NearestFilter,
|
||||
format: THREE.RGBAFormat,
|
||||
type: THREE.UNSIGNED_BYTE
|
||||
})
|
||||
|
||||
let shaderMaterial
|
||||
@@ -23,86 +23,86 @@ let shaderPass
|
||||
|
||||
|
||||
function defShaderMaterial() {
|
||||
shaderMaterial = makeShaderMaterial()
|
||||
shaderMaterial = makeShaderMaterial()
|
||||
|
||||
shaderPass = new THREE.ShaderPass(shaderMaterial)
|
||||
composer.addPass(shaderPass)
|
||||
shaderPass.material.uniforms.tDiffuse.value = renderTarget.texture
|
||||
shaderPass = new THREE.ShaderPass(shaderMaterial)
|
||||
composer.addPass(shaderPass)
|
||||
shaderPass.material.uniforms.tDiffuse.value = renderTarget.texture
|
||||
}
|
||||
|
||||
|
||||
function makeShaderMaterial() {
|
||||
return new THREE.ShaderMaterial({
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
return new THREE.ShaderMaterial({
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec2 vUv;
|
||||
|
||||
uniform vec3 edgeColor;
|
||||
uniform vec3 centerColor;
|
||||
uniform sampler2D tDiffuse;
|
||||
uniform float pxDensity;
|
||||
uniform vec2 resolution;
|
||||
uniform vec3 edgeColor;
|
||||
uniform vec3 centerColor;
|
||||
uniform sampler2D tDiffuse;
|
||||
uniform float pxDensity;
|
||||
uniform vec2 resolution;
|
||||
|
||||
float brightness(vec3 c) {
|
||||
return (c.r + c.g + c.b) / 3.0;
|
||||
}
|
||||
float brightness(vec3 c) {
|
||||
return (c.r + c.g + c.b) / 3.0;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = vUv;
|
||||
void main() {
|
||||
vec2 uv = vUv;
|
||||
|
||||
vec2 screenPxCount = vec2(resolution.x / pxDensity, resolution.y / pxDensity);
|
||||
//screenPxCount = vec2(256.0, 164.0);
|
||||
vec2 p = floor(uv * screenPxCount) / screenPxCount;
|
||||
vec2 screenPxCount = vec2(resolution.x / pxDensity, resolution.y / pxDensity);
|
||||
//screenPxCount = vec2(256.0, 164.0);
|
||||
vec2 p = floor(uv * screenPxCount) / screenPxCount;
|
||||
|
||||
vec4 color = texture2D(tDiffuse, p);
|
||||
vec4 color = texture2D(tDiffuse, p);
|
||||
|
||||
float b = brightness(color.rgb);
|
||||
float centerB = brightness(centerColor);
|
||||
float alpha = floor(color.a);
|
||||
float b = brightness(color.rgb);
|
||||
float centerB = brightness(centerColor);
|
||||
float alpha = floor(color.a);
|
||||
|
||||
if (b > centerB) {
|
||||
gl_FragColor = vec4(edgeColor, color.a);
|
||||
}
|
||||
else if (color == vec4(0.0)) {
|
||||
gl_FragColor = vec4(0.0);
|
||||
}
|
||||
else {
|
||||
gl_FragColor = vec4(centerColor, color.a);
|
||||
}
|
||||
|
||||
//gl_FragColor = color;
|
||||
}
|
||||
`,
|
||||
uniforms: {
|
||||
edgeColor: { value: new THREE.Color(0x00b4f0) },
|
||||
centerColor: { value: new THREE.Color(0x323232) },
|
||||
tDiffuse: { value: null },
|
||||
pxDensity: { value: pxDensity },
|
||||
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
|
||||
}
|
||||
})
|
||||
if (b > centerB) {
|
||||
gl_FragColor = vec4(edgeColor, color.a);
|
||||
}
|
||||
else if (color == vec4(0.0)) {
|
||||
gl_FragColor = vec4(0.0);
|
||||
}
|
||||
else {
|
||||
gl_FragColor = vec4(centerColor, color.a);
|
||||
}
|
||||
|
||||
//gl_FragColor = color;
|
||||
}
|
||||
`,
|
||||
uniforms: {
|
||||
edgeColor: { value: new THREE.Color(0x00b4f0) },
|
||||
centerColor: { value: new THREE.Color(0x323232) },
|
||||
tDiffuse: { value: null },
|
||||
pxDensity: { value: pxDensity },
|
||||
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function updateShaderMatPxDensity() {
|
||||
shaderMaterial.uniforms.resolution.value = new THREE.Vector2(window.innerWidth, window.innerHeight)
|
||||
shaderMaterial.uniforms.pxDensity.value = pxDensity
|
||||
shaderMaterial.uniforms.resolution.value = new THREE.Vector2(window.innerWidth, window.innerHeight)
|
||||
shaderMaterial.uniforms.pxDensity.value = pxDensity
|
||||
}
|
||||
|
||||
|
||||
// TODO: add pixelation shader
|
||||
|
||||
if (!renderer) {
|
||||
alert("Renderer failed to load, using static images instead.")
|
||||
// TODO: add image logic
|
||||
throw new Error("Renderer initialization failed.")
|
||||
alert("Renderer failed to load, using static images instead.")
|
||||
// TODO: add image logic
|
||||
throw new Error("Renderer initialization failed.")
|
||||
}
|
||||
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
@@ -115,15 +115,15 @@ renderer.setClearColor(0x000000, 0)
|
||||
const geometry = new THREE.BufferGeometry()
|
||||
|
||||
let meshVerts = [
|
||||
-1, 0, 1,
|
||||
-1, 1, -1,
|
||||
1, 1, 1,
|
||||
1, 0, -1
|
||||
-1, 0, 1,
|
||||
-1, 1, -1,
|
||||
1, 1, 1,
|
||||
1, 0, -1
|
||||
]
|
||||
|
||||
let meshIndices = [
|
||||
0, 1, 2,
|
||||
1, 2, 3
|
||||
0, 1, 2,
|
||||
1, 2, 3
|
||||
]
|
||||
|
||||
|
||||
@@ -151,162 +151,162 @@ camera.rotation.x = 0.2
|
||||
camera.lookAt(0, 0, 0) */
|
||||
|
||||
debugPrint(
|
||||
"Camera transform",
|
||||
`Pos: [x: ${camera.position.x}, y: ${camera.position.y}, z: ${camera.position.z}]
|
||||
"Camera transform",
|
||||
`Pos: [x: ${camera.position.x}, y: ${camera.position.y}, z: ${camera.position.z}]
|
||||
Rot: [x: ${camera.rotation.x}, y: ${camera.rotation.y}, z: ${camera.rotation.z}]`
|
||||
)
|
||||
|
||||
function hillCurve(x, width) {
|
||||
return (Math.sin(Math.PI * (x + 1)) + 1) ** width
|
||||
return (Math.sin(Math.PI * (x + 1)) + 1) ** width
|
||||
}
|
||||
|
||||
|
||||
function generateHill(width, length, segments, maxHeight, posOffsetZ = 0, prevHillVerts = []) {
|
||||
let hillIndices = []
|
||||
let startPos = -width / 2
|
||||
let triCount = segments + 1
|
||||
let hillIndices = []
|
||||
let startPos = -width / 2
|
||||
let triCount = segments + 1
|
||||
|
||||
let prevHillHeights = prevHillVerts.filter((_, index) => (index - 1) % 3 === 0)
|
||||
let prevHillHeights = prevHillVerts.filter((_, index) => (index - 1) % 3 === 0)
|
||||
|
||||
let hillData = generateHillVerts(
|
||||
triCount,
|
||||
maxHeight,
|
||||
width,
|
||||
startPos,
|
||||
prevHillHeights,
|
||||
segments,
|
||||
length,
|
||||
posOffsetZ
|
||||
)
|
||||
let hillVerts = hillData.verts
|
||||
let hillUVs = hillData.uvs
|
||||
let hillData = generateHillVerts(
|
||||
triCount,
|
||||
maxHeight,
|
||||
width,
|
||||
startPos,
|
||||
prevHillHeights,
|
||||
segments,
|
||||
length,
|
||||
posOffsetZ
|
||||
)
|
||||
let hillVerts = hillData.verts
|
||||
let hillUVs = hillData.uvs
|
||||
|
||||
for (let i = 0; i < segments; i++) {
|
||||
hillIndices.push(i * 2, i * 2 + 2, i * 2 + 1)
|
||||
hillIndices.push(i * 2 + 1, i * 2 + 2, i * 2 + 3)
|
||||
}
|
||||
for (let i = 0; i < segments; i++) {
|
||||
hillIndices.push(i * 2, i * 2 + 2, i * 2 + 1)
|
||||
hillIndices.push(i * 2 + 1, i * 2 + 2, i * 2 + 3)
|
||||
}
|
||||
|
||||
const geometry = new THREE.BufferGeometry()
|
||||
const geometry = new THREE.BufferGeometry()
|
||||
|
||||
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(hillVerts), 3))
|
||||
geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(hillUVs), 2))
|
||||
geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(hillIndices), 1))
|
||||
geometry.computeVertexNormals()
|
||||
geometry.computeBoundingBox()
|
||||
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(hillVerts), 3))
|
||||
geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(hillUVs), 2))
|
||||
geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(hillIndices), 1))
|
||||
geometry.computeVertexNormals()
|
||||
geometry.computeBoundingBox()
|
||||
|
||||
const material = generateHillMat(segments, width, length)
|
||||
const newMesh = new THREE.Mesh(geometry, material)
|
||||
const material = generateHillMat(segments, width, length)
|
||||
const newMesh = new THREE.Mesh(geometry, material)
|
||||
|
||||
newMesh.position.z = posOffsetZ
|
||||
newMesh.position.z = posOffsetZ
|
||||
|
||||
return {mesh: newMesh, verts: hillVerts, indices: hillIndices}
|
||||
return {mesh: newMesh, verts: hillVerts, indices: hillIndices}
|
||||
}
|
||||
|
||||
|
||||
function generateHillVerts(
|
||||
triCount,
|
||||
maxHeight,
|
||||
width,
|
||||
startPos,
|
||||
prevHillHeights,
|
||||
segments,
|
||||
length
|
||||
triCount,
|
||||
maxHeight,
|
||||
width,
|
||||
startPos,
|
||||
prevHillHeights,
|
||||
segments,
|
||||
length
|
||||
) {
|
||||
let hillVerts = []
|
||||
let hillUVs = []
|
||||
let hillVerts = []
|
||||
let hillUVs = []
|
||||
|
||||
for (let i = 0; i < triCount; i++) {
|
||||
let height = randomFloat(0, maxHeight) * hillCurve(i / triCount, 1.2)
|
||||
for (let i = 0; i < triCount; i++) {
|
||||
let height = randomFloat(0, maxHeight) * hillCurve(i / triCount, 1.2)
|
||||
|
||||
hillVerts.push(i / (triCount - 1) * width + startPos)
|
||||
hillVerts.push(i / (triCount - 1) * width + startPos)
|
||||
|
||||
if (prevHillHeights.length / 2 != segments + 1) { hillVerts.push(0) }
|
||||
else { hillVerts.push(prevHillHeights[i * 2 + 1]) }
|
||||
if (prevHillHeights.length / 2 != segments + 1) { hillVerts.push(0) }
|
||||
else { hillVerts.push(prevHillHeights[i * 2 + 1]) }
|
||||
|
||||
hillVerts.push(length / 2)
|
||||
hillVerts.push(length / 2)
|
||||
|
||||
hillUVs.push(i / (triCount - 1), 0)
|
||||
hillUVs.push(i / (triCount - 1), 0)
|
||||
|
||||
hillVerts.push(
|
||||
i / (triCount - 1) * width + startPos,
|
||||
height,
|
||||
-length / 2
|
||||
)
|
||||
hillUVs.push(i / (triCount - 1), 1)
|
||||
}
|
||||
hillVerts.push(
|
||||
i / (triCount - 1) * width + startPos,
|
||||
height,
|
||||
-length / 2
|
||||
)
|
||||
hillUVs.push(i / (triCount - 1), 1)
|
||||
}
|
||||
|
||||
return {verts: hillVerts, uvs: hillUVs}
|
||||
return {verts: hillVerts, uvs: hillUVs}
|
||||
}
|
||||
|
||||
|
||||
function generateHillMat(segments, width, length) {
|
||||
//return new THREE.MeshBasicMaterial({ color: 0x00FF00, wireframe: false })
|
||||
//return new THREE.MeshBasicMaterial({ color: 0x00FF00, wireframe: false })
|
||||
|
||||
const vertexShader = `
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`
|
||||
const vertexShader = `
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`
|
||||
|
||||
const fragmentShader = `
|
||||
varying vec2 vUv;
|
||||
uniform vec3 edgeColor;
|
||||
uniform vec3 centerColor;
|
||||
uniform int segmentCount;
|
||||
uniform vec2 resolution;
|
||||
const fragmentShader = `
|
||||
varying vec2 vUv;
|
||||
uniform vec3 edgeColor;
|
||||
uniform vec3 centerColor;
|
||||
uniform int segmentCount;
|
||||
uniform vec2 resolution;
|
||||
|
||||
void main() {
|
||||
vec2 uv = vUv;
|
||||
uv.x = fract(uv.x * float(segmentCount));
|
||||
void main() {
|
||||
vec2 uv = vUv;
|
||||
uv.x = fract(uv.x * float(segmentCount));
|
||||
|
||||
float edgeThresholdX = 0.075;
|
||||
float edgeThresholdY = 0.075;
|
||||
float edgeThresholdX = 0.075;
|
||||
float edgeThresholdY = 0.075;
|
||||
|
||||
if (resolution.x > resolution.y) {
|
||||
edgeThresholdX *= resolution.y / resolution.x;
|
||||
}
|
||||
else {
|
||||
edgeThresholdY *= resolution.x / resolution.y;
|
||||
}
|
||||
if (resolution.x > resolution.y) {
|
||||
edgeThresholdX *= resolution.y / resolution.x;
|
||||
}
|
||||
else {
|
||||
edgeThresholdY *= resolution.x / resolution.y;
|
||||
}
|
||||
|
||||
float isEdge = 0.0;
|
||||
|
||||
if (uv.y < edgeThresholdY || uv.y > (1.0 - edgeThresholdY)) {
|
||||
isEdge = 1.0;
|
||||
}
|
||||
|
||||
if (uv.x < edgeThresholdX || uv.x > (1.0 - edgeThresholdX)) {
|
||||
isEdge = 1.0;
|
||||
}
|
||||
float isEdge = 0.0;
|
||||
|
||||
vec3 color = mix(centerColor, edgeColor, isEdge);
|
||||
if (uv.y < edgeThresholdY || uv.y > (1.0 - edgeThresholdY)) {
|
||||
isEdge = 1.0;
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
}
|
||||
`
|
||||
if (uv.x < edgeThresholdX || uv.x > (1.0 - edgeThresholdX)) {
|
||||
isEdge = 1.0;
|
||||
}
|
||||
|
||||
const material = new THREE.ShaderMaterial({
|
||||
vertexShader,
|
||||
fragmentShader,
|
||||
uniforms: {
|
||||
edgeColor: { value: new THREE.Color(0x00b4f0) },
|
||||
centerColor: { value: new THREE.Color(0x323232) },
|
||||
segmentCount: { value: segments },
|
||||
resolution: { value: new THREE.Vector2(width / segments, length) }
|
||||
}
|
||||
})
|
||||
vec3 color = mix(centerColor, edgeColor, isEdge);
|
||||
|
||||
return material
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
}
|
||||
`
|
||||
|
||||
const material = new THREE.ShaderMaterial({
|
||||
vertexShader,
|
||||
fragmentShader,
|
||||
uniforms: {
|
||||
edgeColor: { value: new THREE.Color(0x00b4f0) },
|
||||
centerColor: { value: new THREE.Color(0x323232) },
|
||||
segmentCount: { value: segments },
|
||||
resolution: { value: new THREE.Vector2(width / segments, length) }
|
||||
}
|
||||
})
|
||||
|
||||
return material
|
||||
}
|
||||
|
||||
|
||||
let allHillMeshes = []
|
||||
|
||||
function addHillMesh(mesh) {
|
||||
allHillMeshes.push(mesh)
|
||||
scene.add(mesh)
|
||||
allHillMeshes.push(mesh)
|
||||
scene.add(mesh)
|
||||
}
|
||||
|
||||
|
||||
@@ -316,25 +316,25 @@ let lastHillVerts = []
|
||||
let hasPreAnimated = false
|
||||
|
||||
function preAnimateSynthwave(times) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
let hillOffset = -(i * hillLength) + 0.02
|
||||
for (let i = 0; i < times; i++) {
|
||||
let hillOffset = -(i * hillLength) + 0.02
|
||||
|
||||
let newHill = generateHill(
|
||||
15, //camera.aspect * 10,
|
||||
hillLength,
|
||||
25,
|
||||
4,
|
||||
hillOffset,
|
||||
lastHillVerts
|
||||
)
|
||||
let newHill = generateHill(
|
||||
15, //camera.aspect * 10,
|
||||
hillLength,
|
||||
25,
|
||||
4,
|
||||
hillOffset,
|
||||
lastHillVerts
|
||||
)
|
||||
|
||||
addHillMesh(newHill.mesh)
|
||||
lastHillVerts = newHill.verts
|
||||
hillSpawnOffset = hillOffset
|
||||
}
|
||||
addHillMesh(newHill.mesh)
|
||||
lastHillVerts = newHill.verts
|
||||
hillSpawnOffset = hillOffset
|
||||
}
|
||||
|
||||
hasPreAnimated = true
|
||||
debugPrint("Pre-animated synthwave complete")
|
||||
hasPreAnimated = true
|
||||
debugPrint("Pre-animated synthwave complete")
|
||||
}
|
||||
|
||||
|
||||
@@ -344,69 +344,69 @@ let tickMultiplier = 1
|
||||
let distSinceLastHill = 0
|
||||
|
||||
function animateSynthwave(timestamp) {
|
||||
let deltaTime = (timestamp - lastTime) / 1000
|
||||
lastTime = timestamp
|
||||
let deltaTime = (timestamp - lastTime) / 1000
|
||||
lastTime = timestamp
|
||||
|
||||
let currentTickRate = 1 / deltaTime
|
||||
tickMultiplier = tickRate / currentTickRate
|
||||
let currentTickRate = 1 / deltaTime
|
||||
tickMultiplier = tickRate / currentTickRate
|
||||
|
||||
if (!hasPreAnimated) { return }
|
||||
if (!hasPreAnimated) { return }
|
||||
|
||||
if (!isAnimating) {
|
||||
render()
|
||||
return
|
||||
}
|
||||
|
||||
const moveDist = 0.0035 //0.05 * tickMultiplier
|
||||
distSinceLastHill += moveDist
|
||||
if (!isAnimating) {
|
||||
render()
|
||||
return
|
||||
}
|
||||
|
||||
for (let hill of allHillMeshes) {
|
||||
hill.position.z += moveDist
|
||||
|
||||
const outOfBoundsCoord = 5
|
||||
if (hill.position.z - hill.geometry.boundingBox.min.y >= outOfBoundsCoord) {
|
||||
scene.remove(hill)
|
||||
allHillMeshes.splice(allHillMeshes.indexOf(hill), 1)
|
||||
continue
|
||||
}
|
||||
}
|
||||
const moveDist = 0.0035 //0.05 * tickMultiplier
|
||||
distSinceLastHill += moveDist
|
||||
|
||||
if (distSinceLastHill >= hillLength) {
|
||||
for (let hill of allHillMeshes) {
|
||||
hill.position.z += moveDist
|
||||
|
||||
let hillZOffset = hillLength - distSinceLastHill
|
||||
const outOfBoundsCoord = 5
|
||||
if (hill.position.z - hill.geometry.boundingBox.min.y >= outOfBoundsCoord) {
|
||||
scene.remove(hill)
|
||||
allHillMeshes.splice(allHillMeshes.indexOf(hill), 1)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let newHill = generateHill(
|
||||
15, //camera.aspect * 10,
|
||||
hillLength,
|
||||
25,
|
||||
4,
|
||||
hillZOffset + 0.02 + hillSpawnOffset,
|
||||
lastHillVerts
|
||||
)
|
||||
if (distSinceLastHill >= hillLength) {
|
||||
|
||||
addHillMesh(newHill.mesh)
|
||||
distSinceLastHill = -hillZOffset
|
||||
lastHillVerts = newHill.verts
|
||||
}
|
||||
let hillZOffset = hillLength - distSinceLastHill
|
||||
|
||||
render()
|
||||
let newHill = generateHill(
|
||||
15, //camera.aspect * 10,
|
||||
hillLength,
|
||||
25,
|
||||
4,
|
||||
hillZOffset + 0.02 + hillSpawnOffset,
|
||||
lastHillVerts
|
||||
)
|
||||
|
||||
addHillMesh(newHill.mesh)
|
||||
distSinceLastHill = -hillZOffset
|
||||
lastHillVerts = newHill.verts
|
||||
}
|
||||
|
||||
render()
|
||||
}
|
||||
|
||||
|
||||
function render() {
|
||||
if (usePostProcessing) {
|
||||
renderer.setRenderTarget(renderTarget)
|
||||
renderer.render(scene, camera)
|
||||
if (usePostProcessing) {
|
||||
renderer.setRenderTarget(renderTarget)
|
||||
renderer.render(scene, camera)
|
||||
|
||||
renderer.setRenderTarget(null)
|
||||
composer.render()
|
||||
renderer.setRenderTarget(null)
|
||||
composer.render()
|
||||
|
||||
requestAnimationFrame(animateSynthwave)
|
||||
}
|
||||
else {
|
||||
renderer.setRenderTarget(null)
|
||||
renderer.render(scene, camera)
|
||||
requestAnimationFrame(animateSynthwave)
|
||||
}
|
||||
else {
|
||||
renderer.setRenderTarget(null)
|
||||
renderer.render(scene, camera)
|
||||
|
||||
requestAnimationFrame(animateSynthwave)
|
||||
}
|
||||
requestAnimationFrame(animateSynthwave)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user