mirror of
https://github.com/wahyd4/three.js.git
synced 2026-08-09 04:56:01 +10:00
Added webgl terrain example.
This commit is contained in:
@@ -35,6 +35,7 @@ More? irc.freenode.net #three.js
|
||||
|
||||
#### WebGL ####
|
||||
|
||||
[](http://mrdoob.github.com/three.js/examples/geometry_terrain_gl.html)
|
||||
[](http://mrdoob.github.com/three.js/examples/geometry_minecraft.html)
|
||||
[](http://mrdoob.github.com/three.js/examples/materials_shaders_fresnel.html)
|
||||
[](http://mrdoob.github.com/three.js/examples/materials_cars.html)
|
||||
@@ -50,6 +51,7 @@ More? irc.freenode.net #three.js
|
||||
|
||||
### Featured projects ###
|
||||
|
||||
[](http://antimatter15.com/wp/2010/11/digital-sculpting-with-three-js/)
|
||||
[](http://mrdoob.com/projects/voxels/)
|
||||
[](http://thewildernessdowntown.com/)
|
||||
[](http://cloudscad.com/stl_viewer/)
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<body>
|
||||
|
||||
<div id="container"><br /><br /><br /><br /><br />Generating world...</div>
|
||||
<div id="info"><a href="http://github.com/mrdoob/three.js" target="_blank">three.js</a> - <a href="http://www.minecraft.net/" target="_blank">minecraft</a> demo. featuring <a href="http://painterlypack.net/" target="_blank">painterly pack</a><br />(camera move: WASD/↑←↓→)</div>
|
||||
<div id="info"><a href="http://github.com/mrdoob/three.js" target="_blank">three.js</a> - <a href="http://www.minecraft.net/" target="_blank">minecraft</a> demo. featuring <a href="http://painterlypack.net/" target="_blank">painterly pack</a><br />(right click: forward, left click: backward)</div>
|
||||
|
||||
<script type="text/javascript" src="js/Stats.js"></script>
|
||||
<script type="text/javascript" src="js/ImprovedNoise.js"></script>
|
||||
@@ -56,8 +56,7 @@
|
||||
lat = 0, lon = 0, phy = 0, theta = 0;
|
||||
|
||||
var direction = new THREE.Vector3(),
|
||||
moveForward = false, moveBackwards = false,
|
||||
moveLeft = false, moveRight = false;
|
||||
moveForward = false, moveBackwards = false;
|
||||
|
||||
var windowHalfX = window.innerWidth / 2;
|
||||
var windowHalfY = window.innerHeight / 2;
|
||||
@@ -115,12 +114,12 @@
|
||||
var ambientLight = new THREE.AmbientLight( 0xcccccc );
|
||||
scene.addLight( ambientLight );
|
||||
|
||||
var pointLight = new THREE.DirectionalLight( 0xffffff, 1.5 );
|
||||
pointLight.position.x = 1;
|
||||
pointLight.position.y = 1;
|
||||
pointLight.position.z = 0.5;
|
||||
pointLight.position.normalize;
|
||||
scene.addLight( pointLight );
|
||||
var directionalLight = new THREE.DirectionalLight( 0xffffff, 1.5 );
|
||||
directionalLight.position.x = 1;
|
||||
directionalLight.position.y = 1;
|
||||
directionalLight.position.z = 0.5;
|
||||
directionalLight.position.normalize();
|
||||
scene.addLight( directionalLight );
|
||||
|
||||
renderer = new THREE.WebGLRenderer();
|
||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
||||
@@ -134,7 +133,11 @@
|
||||
stats.domElement.style.top = '0px';
|
||||
container.appendChild( stats.domElement );
|
||||
|
||||
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
|
||||
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
|
||||
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
|
||||
document.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
|
||||
|
||||
document.addEventListener( 'keydown', onDocumentKeyDown, false );
|
||||
document.addEventListener( 'keyup', onDocumentKeyUp, false );
|
||||
|
||||
@@ -181,6 +184,34 @@
|
||||
|
||||
}
|
||||
|
||||
function onDocumentMouseDown( event ) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
switch ( event.button ) {
|
||||
|
||||
case 0: moveForward = true; break;
|
||||
case 2: moveBackwards = true; break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function onDocumentMouseUp( event ) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
switch ( event.button ) {
|
||||
|
||||
case 0: moveForward = false; break;
|
||||
case 2: moveBackwards = false; break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function onDocumentMouseMove(event) {
|
||||
|
||||
mouseX = event.clientX - windowHalfX;
|
||||
@@ -189,20 +220,14 @@
|
||||
}
|
||||
|
||||
function onDocumentKeyDown( event ) {
|
||||
|
||||
// console.log( event.keyCode );
|
||||
|
||||
switch( event.keyCode ) {
|
||||
|
||||
case 38: /*↑*/ moveForward = true; break;
|
||||
case 40: /*↓*/ moveBackwards = true; break;
|
||||
case 37: /*←*/ moveLeft = true; break;
|
||||
case 39: /*→*/ moveRight = true; break;
|
||||
|
||||
case 87: /*W*/ moveForward = true; break;
|
||||
case 83: /*S*/ moveBackwards = true; break;
|
||||
case 65: /*A*/ moveLeft = true; break;
|
||||
case 68: /*D*/ moveRight = true; break;
|
||||
|
||||
}
|
||||
|
||||
@@ -214,13 +239,9 @@
|
||||
|
||||
case 38: /*↑*/ moveForward = false; break;
|
||||
case 40: /*↓*/ moveBackwards = false; break;
|
||||
case 37: /*←*/ moveLeft = false; break;
|
||||
case 39: /*→*/ moveRight = false; break;
|
||||
|
||||
case 87: /*W*/ moveForward = false; break;
|
||||
case 83: /*S*/ moveBackwards = false; break;
|
||||
case 65: /*A*/ moveLeft = false; break;
|
||||
case 68: /*D*/ moveRight = false; break;
|
||||
|
||||
}
|
||||
|
||||
@@ -244,24 +265,6 @@
|
||||
|
||||
}
|
||||
|
||||
if ( moveLeft ) {
|
||||
|
||||
direction.copy( camera.target.position ).subSelf( camera.position ).normalize().multiplyScalar( - 15 );
|
||||
direction.cross( direction.clone(), new THREE.Vector3( 0, 1, 0 ) );
|
||||
camera.position.addSelf( direction );
|
||||
camera.target.position.addSelf( direction );
|
||||
|
||||
}
|
||||
|
||||
if ( moveRight ) {
|
||||
|
||||
direction.copy( camera.target.position ).subSelf( camera.position ).normalize().multiplyScalar( 15 );
|
||||
direction.cross( direction.clone(), new THREE.Vector3( 0, 1, 0 ) );
|
||||
camera.position.addSelf( direction );
|
||||
camera.target.position.addSelf( direction );
|
||||
|
||||
};
|
||||
|
||||
lon += mouseX * 0.005;
|
||||
lat -= mouseY * 0.005;
|
||||
|
||||
|
||||
@@ -67,28 +67,16 @@
|
||||
|
||||
scene = new THREE.Scene();
|
||||
|
||||
var heightMap = height( 1024, 1024 );
|
||||
var data = generateHeight( 1024, 1024 );
|
||||
var material = new THREE.MeshBasicMaterial( { map: new THREE.Texture( generateTexture( data, 1024, 1024 ) ) } );
|
||||
|
||||
var texture = new THREE.Texture( shadow( heightMap ) );
|
||||
texture.image.loaded = true;
|
||||
var quality = 16, step = 1024 / quality;
|
||||
var plane = new Plane( 2000, 2000, quality - 1, quality - 1 );
|
||||
|
||||
var material = new THREE.MeshBasicMaterial( { map: texture } );
|
||||
for ( var i = 0, l = plane.vertices.length; i < l; i ++ ) {
|
||||
|
||||
var quality = 20;
|
||||
var quality1 = quality + 1;
|
||||
|
||||
var plane = new Plane( 2000, 2000, quality, quality );
|
||||
|
||||
var data = heightMap.getContext( '2d' ).getImageData( 0, 0, heightMap.width, heightMap.height ).data;
|
||||
var pixelsPerTriangle = Math.floor( heightMap.width / quality );
|
||||
|
||||
for ( var y = 0; y < quality1; y++ ) {
|
||||
|
||||
for (var x = 0; x < quality1; x++ ) {
|
||||
|
||||
plane.vertices[x + y * quality1].position.z = data[((x * pixelsPerTriangle) + (y * pixelsPerTriangle) * heightMap.width) * 4] * 2 - 128;
|
||||
|
||||
}
|
||||
var x = i % quality, y = ~~ ( i / quality );
|
||||
plane.vertices[ i ].position.z = data[ ( x * step ) + ( y * step ) * 1024 ] * 2 - 128;
|
||||
|
||||
}
|
||||
|
||||
@@ -98,7 +86,7 @@
|
||||
mesh = new THREE.Mesh( plane, material );
|
||||
mesh.rotation.x = -90 * Math.PI / 180;
|
||||
mesh.overdraw = true;
|
||||
scene.addObject(mesh);
|
||||
scene.addObject( mesh );
|
||||
|
||||
renderer = new THREE.CanvasRenderer();
|
||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
||||
@@ -133,60 +121,39 @@
|
||||
|
||||
}
|
||||
|
||||
function height( width, height ) {
|
||||
function generateHeight( width, height ) {
|
||||
|
||||
var canvas, context, image, data;
|
||||
var data = Float32Array ? new Float32Array() : [], perlin = new ImprovedNoise(),
|
||||
size = width * height, quality = 2, z = Math.random() * 100;
|
||||
|
||||
canvas = document.createElement( 'canvas' );
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
for ( var i = 0; i < size; i ++ ) {
|
||||
|
||||
context = canvas.getContext( '2d' );
|
||||
context.fillStyle = '#000';
|
||||
context.fillRect( 0, 0, width, height );
|
||||
data[ i ] = 0
|
||||
|
||||
image = context.getImageData( 0, 0, width, height );
|
||||
data = image.data;
|
||||
|
||||
var perlin = new ImprovedNoise();
|
||||
|
||||
var size = width * height;
|
||||
var quality = 2;
|
||||
var zz = Math.random() * 100;
|
||||
}
|
||||
|
||||
for ( var j = 0; j < 4; j ++ ) {
|
||||
|
||||
var x = 0, y = 0, yy = 0;
|
||||
|
||||
quality *= 4;
|
||||
|
||||
for ( var i = 0; i < size; i ++ ) {
|
||||
|
||||
data[ i * 4 ] += Math.floor( Math.abs( perlin.noise( x / quality, yy, zz ) * 0.5 ) * quality + 10 );
|
||||
var x = i % width, y = ~~ ( i / width );
|
||||
data[ i ] += Math.abs( perlin.noise( x / quality, y / quality, z ) * 0.5 * quality + 10 );
|
||||
|
||||
x++;
|
||||
|
||||
if (x == width)
|
||||
{
|
||||
x = 0;
|
||||
y ++;
|
||||
yy = y / quality;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
context.putImageData( image, 0, 0 );
|
||||
|
||||
return canvas;
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
function shadow( texture ) {
|
||||
function generateTexture( data, width, height ) {
|
||||
|
||||
var canvas, context, image, data, src_data,
|
||||
index, level, diff, width = texture.width, height = texture.height,
|
||||
vector3, sun, shade;
|
||||
var canvas, context, image, imageData,
|
||||
level, diff, vector3, sun, shade;
|
||||
|
||||
vector3 = new THREE.Vector3( 0, 0, 0 );
|
||||
|
||||
@@ -196,39 +163,34 @@
|
||||
canvas = document.createElement( 'canvas' );
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvas.loaded = true;
|
||||
|
||||
context = canvas.getContext( '2d' );
|
||||
context.fillStyle = '#000';
|
||||
context.fillRect( 0, 0, width, height );
|
||||
|
||||
image = context.getImageData( 0, 0, width, height );
|
||||
data = image.data;
|
||||
imageData = image.data;
|
||||
|
||||
src_data = texture.getContext( '2d' ).getImageData( 0, 0, width, height ).data;
|
||||
for ( var i = 0, j = 0, l = imageData.length; i < l; i += 4, j ++ ) {
|
||||
|
||||
for ( var y = 0; y < height; y++ ) {
|
||||
vector3.x = data[ j - 1 ] - data[ j + 1 ];
|
||||
vector3.y = 2;
|
||||
vector3.z = data[ j - width ] - data[ j + width ];
|
||||
vector3.normalize();
|
||||
|
||||
for ( var x = 0; x < width; x++ ) {
|
||||
shade = vector3.dot( sun );
|
||||
|
||||
index = ( x + y * width ) * 4;
|
||||
imageData[ i ] = ( 96 + shade * 128 ) * ( data[ j ] * 0.007 );
|
||||
imageData[ i + 1 ] = ( 32 + shade * 96 ) * ( data[ j ] * 0.007 );
|
||||
imageData[ i + 2 ] = ( shade * 96 ) * ( data[ j ] * 0.007 );
|
||||
|
||||
vector3.x = src_data[ index - 4 ] - src_data[ index + 4 ];
|
||||
vector3.y = 2;
|
||||
vector3.z = src_data[ index - ( width * 4 ) ] - src_data[ index + ( width * 4 ) ];
|
||||
vector3.normalize();
|
||||
|
||||
shade = vector3.dot(sun);
|
||||
|
||||
data[ index ] = ( 96 + shade * 128 ) * ( src_data[ index ] * 0.007 );
|
||||
data[ index + 1 ] = ( 32 + shade * 96 ) * ( src_data[ index ] * 0.007 );
|
||||
data[ index + 2 ] = ( shade * 96 ) * ( src_data[ index ] * 0.007 );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
context.putImageData( image, 0, 0 );
|
||||
|
||||
return canvas;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>three.js - geometry - webgl terrain</title>
|
||||
<meta charset="utf-8">
|
||||
<style type="text/css">
|
||||
body {
|
||||
color: #61443e;
|
||||
font-family:Monospace;
|
||||
font-size:13px;
|
||||
text-align:center;
|
||||
|
||||
background-color: #bfd1e5;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#info {
|
||||
position: absolute;
|
||||
top: 0px; width: 100%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
color: #a06851;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="container"><br /><br /><br /><br /><br />Generating world...</div>
|
||||
<div id="info"><a href="http://github.com/mrdoob/three.js" target="_blank">three.js</a> - webgl terrain demo<br />(right click: forward, left click: backward)</div>
|
||||
|
||||
<script type="text/javascript" src="js/Stats.js"></script>
|
||||
<script type="text/javascript" src="js/ImprovedNoise.js"></script>
|
||||
|
||||
<script type="text/javascript" src="../build/Three.js"></script>
|
||||
<script type="text/javascript" src="../src/extras/GeometryUtils.js"></script>
|
||||
<script type="text/javascript" src="../src/extras/primitives/Plane.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var container, stats;
|
||||
|
||||
var camera, scene, renderer;
|
||||
|
||||
var mesh, texture;
|
||||
|
||||
var worldWidth = 256, worldDepth = 256,
|
||||
worldHalfWidth = worldWidth / 2, worldHalfDepth = worldDepth / 2;
|
||||
|
||||
var mouseX = 0, mouseY = 0,
|
||||
lat = 0, lon = 0, phy = 0, theta = 0;
|
||||
|
||||
var direction = new THREE.Vector3(),
|
||||
moveForward = false, moveBackwards = false;
|
||||
|
||||
var windowHalfX = window.innerWidth / 2;
|
||||
var windowHalfY = window.innerHeight / 2;
|
||||
|
||||
|
||||
init();
|
||||
setInterval( loop, 1000 / 60 );
|
||||
|
||||
|
||||
function init() {
|
||||
|
||||
container = document.getElementById( 'container' );
|
||||
|
||||
camera = new THREE.Camera( 60, window.innerWidth / window.innerHeight, 1, 20000 );
|
||||
camera.target.position.z = - 100;
|
||||
|
||||
scene = new THREE.Scene();
|
||||
|
||||
data = generateHeight( worldWidth, worldDepth );
|
||||
|
||||
camera.position.y = data[ worldHalfWidth + worldHalfDepth * worldWidth ] + 500;
|
||||
camera.target.position.y = camera.position.y;
|
||||
|
||||
var geometry = new Plane( 7500, 7500, worldWidth - 1, worldDepth - 1 );
|
||||
|
||||
for ( var i = 0, l = geometry.vertices.length; i < l; i ++ ) {
|
||||
|
||||
geometry.vertices[ i ].position.z = data[ i ] * 10;
|
||||
|
||||
}
|
||||
|
||||
geometry.computeNormals();
|
||||
geometry.computeVertexNormals();
|
||||
geometry.sortFacesByMaterial();
|
||||
|
||||
texture = new THREE.Texture( generateTexture( data, worldWidth, worldDepth ), new THREE.UVMapping(), THREE.ClampToEdgeWrapping, THREE.ClampToEdgeWrapping );
|
||||
|
||||
mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { map: texture } ) );
|
||||
mesh.rotation.x = - 90 * Math.PI / 180;
|
||||
scene.addObject( mesh );
|
||||
|
||||
renderer = new THREE.WebGLRenderer();
|
||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
||||
|
||||
container.innerHTML = "";
|
||||
|
||||
container.appendChild( renderer.domElement );
|
||||
|
||||
stats = new Stats();
|
||||
stats.domElement.style.position = 'absolute';
|
||||
stats.domElement.style.top = '0px';
|
||||
container.appendChild( stats.domElement );
|
||||
|
||||
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
|
||||
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
|
||||
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
|
||||
document.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
|
||||
|
||||
}
|
||||
|
||||
function generateHeight( width, height ) {
|
||||
|
||||
var data = Float32Array ? new Float32Array() : [], perlin = new ImprovedNoise(),
|
||||
size = width * height, quality = 1, z = Math.random() * 100;
|
||||
|
||||
for ( var i = 0; i < size; i ++ ) {
|
||||
|
||||
data[ i ] = 0
|
||||
|
||||
}
|
||||
|
||||
for ( var j = 0; j < 4; j ++ ) {
|
||||
|
||||
for ( var i = 0; i < size; i ++ ) {
|
||||
|
||||
var x = i % width, y = ~~ ( i / width );
|
||||
data[ i ] += Math.abs( perlin.noise( x / quality, y / quality, z ) * quality * 1.75 );
|
||||
|
||||
|
||||
}
|
||||
|
||||
quality *= 5;
|
||||
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
function generateTexture( data, width, height ) {
|
||||
|
||||
var canvas, context, image, imageData,
|
||||
level, diff, vector3, sun, shade;
|
||||
|
||||
vector3 = new THREE.Vector3( 0, 0, 0 );
|
||||
|
||||
sun = new THREE.Vector3( 1, 1, 1 );
|
||||
sun.normalize();
|
||||
|
||||
canvas = document.createElement( 'canvas' );
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvas.loaded = true;
|
||||
|
||||
context = canvas.getContext( '2d' );
|
||||
context.fillStyle = '#000';
|
||||
context.fillRect( 0, 0, width, height );
|
||||
|
||||
image = context.getImageData( 0, 0, width, height );
|
||||
imageData = image.data;
|
||||
|
||||
for ( var i = 0, j = 0, l = imageData.length; i < l; i += 4, j ++ ) {
|
||||
|
||||
vector3.x = data[ j - 1 ] - data[ j + 1 ];
|
||||
vector3.y = 2;
|
||||
vector3.z = data[ j - width ] - data[ j + width ];
|
||||
vector3.normalize();
|
||||
|
||||
shade = vector3.dot( sun );
|
||||
|
||||
imageData[ i ] = ( 96 + shade * 128 ) * ( 0.5 + data[ j ] * 0.007 );
|
||||
imageData[ i + 1 ] = ( 32 + shade * 96 ) * ( 0.5 + data[ j ] * 0.007 );
|
||||
imageData[ i + 2 ] = ( shade * 96 ) * ( 0.5 + data[ j ] * 0.007 );
|
||||
}
|
||||
|
||||
context.putImageData( image, 0, 0 );
|
||||
|
||||
return canvas;
|
||||
|
||||
}
|
||||
|
||||
function onDocumentMouseDown( event ) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
switch ( event.button ) {
|
||||
|
||||
case 0: moveForward = true; break;
|
||||
case 2: moveBackwards = true; break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function onDocumentMouseUp( event ) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
switch ( event.button ) {
|
||||
|
||||
case 0: moveForward = false; break;
|
||||
case 2: moveBackwards = false; break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function onDocumentMouseMove(event) {
|
||||
|
||||
mouseX = event.clientX - windowHalfX;
|
||||
mouseY = event.clientY - windowHalfY;
|
||||
|
||||
}
|
||||
|
||||
function loop() {
|
||||
|
||||
if ( moveForward ) {
|
||||
|
||||
direction.copy( camera.target.position ).subSelf( camera.position ).normalize().multiplyScalar( 15 );
|
||||
camera.position.addSelf( direction );
|
||||
camera.target.position.addSelf( direction );
|
||||
|
||||
}
|
||||
|
||||
if ( moveBackwards ) {
|
||||
|
||||
direction.copy( camera.target.position ).subSelf( camera.position ).normalize().multiplyScalar( - 15 );
|
||||
camera.position.addSelf( direction );
|
||||
camera.target.position.addSelf( direction );
|
||||
|
||||
}
|
||||
|
||||
lon += mouseX * 0.004;
|
||||
lat -= mouseY * 0.004;
|
||||
|
||||
lat = Math.max( - 85, Math.min( 85, lat ) );
|
||||
phi = ( 90 - lat ) * Math.PI / 180;
|
||||
theta = lon * Math.PI / 180;
|
||||
|
||||
camera.target.position.x = 100 * Math.sin( phi ) * Math.cos( theta ) + camera.position.x;
|
||||
camera.target.position.y = 100 * Math.cos( phi ) + camera.position.y;
|
||||
camera.target.position.z = 100 * Math.sin( phi ) * Math.sin( theta ) + camera.position.z;
|
||||
|
||||
renderer.render(scene, camera);
|
||||
stats.update();
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user