Converting THREE.js Geometry into BufferGeometry? - three.js

I'm relatively new to THREE.js and I got this code but I'd like to reconstruct this Geometry into BufferGeometry to get the efficiency benefits. I saw this (var bufferGeometry = new THREE.BufferGeometry().fromGeometry( geometry );) as a possible solution but I could not implement it I'm sure it's simple I just lack the experience with THREE.js to recognize this.
let rainGeo = new THREE.Geometry()
for (let i = 0; i < rainCount; i++) {
rainDrop = new THREE.Vector3(
Math.random() * 120 - 60,
Math.random() * 180 - 80,
Math.random() * 130 - 60,
)
rainDrop.velocity = {}
rainDrop.velocity = 0
bufferGeometry.vertices.push(rainDrop)
}
rainMaterial = new THREE.PointsMaterial({
color: '#ffffff',
size: .3,
transparent: true,
map: THREE.ImageUtils.loadTexture(
'images/snow_mask_2.png'),
blending: THREE.AdditiveBlending,
})
rain = new THREE.Points(bufferGeometry, rainMaterial)
rain.rotation.x = -1.5707963267948963
rain.rotation.y = -3.22
scene.add(rain)
function rainVariation() {
bufferGeometry.vertices.forEach(p => {
p.velocity -= 0.1 + Math.random() * 0.1;
p.y += p.velocity;
if (p.y < -60) {
p.y = 60;
p.velocity = 0;
}
});
bufferGeometry.verticesNeedUpdate = true;
rain.rotation.y += 0.008
}

Try it with this ported code as a basis. I suggest that you manage the velocity per raindrop in a separate array (since these data are not necessary in the shader).
let camera, scene, renderer, rain;
const vertex = new THREE.Vector3();
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.z = 100;
scene = new THREE.Scene();
const geometry = new THREE.BufferGeometry();
const vertices = [];
for (let i = 0; i < 1000; i++) {
vertices.push(
Math.random() * 120 - 60,
Math.random() * 180 - 80,
Math.random() * 130 - 60
);
}
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
const material = new THREE.PointsMaterial( { color: '#ffffff' } );
rain = new THREE.Points( geometry, material );
scene.add(rain);
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
}
function rainVariation() {
var positionAttribute = rain.geometry.getAttribute( 'position' );
for ( var i = 0; i < positionAttribute.count; i ++ ) {
vertex.fromBufferAttribute( positionAttribute, i );
vertex.y -= 1;
if (vertex.y < - 60) {
vertex.y = 90;
}
positionAttribute.setXYZ( i, vertex.x, vertex.y, vertex.z );
}
positionAttribute.needsUpdate = true;
}
function animate() {
requestAnimationFrame( animate );
rainVariation();
renderer.render( scene, camera );
}
body {
margin: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.115/build/three.js"></script>

Related

Three.js moving the camera left and right side of the scene

I am working on a three.js prototype in which a 3d model of the train is added to the scene. My requirement is to move the camera either left / right side of the scene to view the complete train.
I tried using below code -
function onKeyDown(){
var zdelta = 20;
switch( event.keyCode ) {
case 65: // look left
camera.position.z = camera.position.z + zdelta;
}
}
But the scene was rotating rather than panning in the left side.
So it will be great help, if anyone shares their idea on this :)
Thanks,
Satheesh K
Using a keydown event listener is definitely the right approach. Try it with this code:
var scene, camera, renderer, cubeObj;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 400;
camera.position.y = - 10;
camera.position.x = 10;
var cubeGeo = new THREE.BoxGeometry( 200, 150, 100 );
var cubeGeoMaterial = new THREE.MeshPhongMaterial( { color: 0x808080 } );
cubeObj = new THREE.Mesh( cubeGeo, cubeGeoMaterial );
cubeObj.position.x = - 70;
cubeObj.position.y = - 50;
cubeObj.position.z = 0;
cubeObj.rotation.x = 0.5;
scene.add( cubeObj );
var cubeGeo2 = new THREE.BoxGeometry( 200, 150, 100 );
var cubeGeoMaterial2 = new THREE.MeshPhongMaterial( { color: 0x808080 } );
var cubeObj2 = new THREE.Mesh( cubeGeo2, cubeGeoMaterial2 );
cubeObj2.position.x = 160;
cubeObj2.position.y = - 50;
cubeObj2.position.z = - 5;
cubeObj2.rotation.x = 0.5;
scene.add( cubeObj2 );
var cubeGeo3 = new THREE.BoxGeometry( 200, 150, 100 );
var cubeGeoMaterial3 = new THREE.MeshPhongMaterial( { color: 0x808080 } );
var cubeObj3 = new THREE.Mesh( cubeGeo3, cubeGeoMaterial3 );
cubeObj3.position.x = 440;
cubeObj3.position.y = - 50;
cubeObj3.position.z = 0;
cubeObj3.rotation.x = 0.5;
scene.add( cubeObj3 );
renderer = new THREE.WebGLRenderer( {
antialias: true
} );
var spotLight = new THREE.SpotLight( 0xffffff );
spotLight.position.set( 100, 1000, 100 );
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.camera.near = 500;
spotLight.shadow.camera.far = 4000;
spotLight.shadow.camera.fov = 30;
//scene.add( spotLight );
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
scene.add( directionalLight );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xcce0ff );
document.body.appendChild( renderer.domElement );
document.addEventListener( 'keydown', onKeyDown, false );
}
function onKeyDown( event ) {
const step = 5; // world units
switch ( event.keyCode ) {
case 37:
camera.position.x -= step;
break;
case 39:
camera.position.x += step;
break;
}
}
function animate() {
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
body {
margin: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.114/build/three.js"></script>

Three.js particles

I've been trying to generate a particle system recently and I've been struggling to get it to work as it's based from an outdated version of three.js, it isn't appearing in the scene and I'm not sure why. It's probably obvious to why but I'm not that good at this.
var particleCount = 1800,
particles = new THREE.Geometry(),
pMaterial = new THREE.PointsMaterial({
size: 20,
map: THREE.TextureLoader("x.png"),
blending: THREE.AdditiveBlending,
transparent: true
});
var particleCount = 500,
particleSystem;
init();
render();
function init() {
for (var p = 0; p < particleCount; p++) {
(pX = Math.random() * 500 - 250),
(pY = Math.random() * 500 - 250),
(pZ = Math.random() * 500 - 250),
(particle = new THREE.Vector3(new THREE.Vector3(pX, pY, pZ)));
particle.velocity = new THREE.Vector3(0, Math.random(), 0);
particles.vertices.push(particle);
}
particleSystem = new THREE.Points(particles, pMaterial);
particleSystem.sortParticles = true;
scene.add(particleSystem);
particleSystem.position.set(0, 0, 0);
particleSystem.scale.set(100, 100, 100);
}
function update() {
particleSystem.rotation.y += 0.01;
pCount = particleCount;
while (pCount--) {
particle = particles.vertices[pCount];
if (particle.y < -200) {
particle.y = 200;
particle.velocity.y = 0;
}
particle.velocity.y -= Math.random() * 0.1;
particle.add(particle.velocity);
}
particleSystem.geometry.__dirtyVertices = true;
renderer.render(scene, camera);
}
I might be missing a few things as this is a few lines I had to pick out from a few hundred.
(I'm new here so please don't bully me for awful structure.)
Thanks in advance for anyone who responds.
map: THREE.TextureLoader("x.png"), should be map: new THREE.TextureLoader().load("x.png"),
particle = new THREE.Vector3(new THREE.Vector3(pX, pY, pZ)); should be particle = new THREE.Vector3(pX, pY, pZ);
particleSystem.geometry.__dirtyVertices = true; is outdated, you have to use particleSystem.geometry.verticesNeedUpdate = true;
Add depthTest: false to points material
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 400);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var particleCount = 1800,
particles = new THREE.Geometry(),
pMaterial = new THREE.PointsMaterial({
size: 20,
map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/sprites/circle.png"),
blending: THREE.AdditiveBlending,
transparent: true,
depthTest: false
});
var particleCount = 500,
particleSystem;
for (var p = 0; p < particleCount; p++) {
pX = Math.random() * 500 - 250,
pY = Math.random() * 500 - 250,
pZ = Math.random() * 500 - 250,
particle = new THREE.Vector3(pX, pY, pZ);
particle.velocity = new THREE.Vector3(0, Math.random(), 0);
particles.vertices.push(particle);
}
particleSystem = new THREE.Points(particles, pMaterial);
scene.add(particleSystem);
function update() {
particleSystem.rotation.y += 0.01;
pCount = particleCount;
while (pCount--) {
particle = particles.vertices[pCount];
if (particle.y < -200) {
particle.y = 200;
particle.velocity.y = 0;
}
particle.velocity.y -= Math.random() * .1;
particle.add(particle.velocity);
}
particleSystem.geometry.verticesNeedUpdate = true;
}
renderer.setAnimationLoop(() => {
update();
renderer.render(scene, camera);
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.js"></script>

Three.js, moving a partical along an EllipseCurve

I know questions related to my problem have been asked and answered before but three.js changed a lot in the last couple years and I'm having trouble finding what I need in the currently available examples.
I have an elliptical curve that I'd like to run particles along. My code runs without error but it doesn't actually move the particle anywhere. What am I missing?
var t = 0;
var curve = new THREE.EllipseCurve( .37, .15, .35, .25, 150, 450, false, 0 );
var points = curve.getPoints( 50 );
var curveGeometry = new THREE.BufferGeometry().setFromPoints( points );
var particleGeometry = new THREE.Geometry();
var particleMap = new THREE.TextureLoader().load( "/img/spark.png" );
var vertex = new THREE.Vector3();
vertex.x = points[0].x;
vertex.y = points[0].y;
vertex.z = 0;
particleGeometry.vertices.push(vertex);
particleMaterial = new THREE.PointsMaterial({
size: .05,
map: particleMap,
blending: THREE.AdditiveBlending,
depthTest: false,
transparent : true
});
particles = new THREE.Points( particleGeometry, particleMaterial );
scene.add(particles);
animate();
function animate() {
if (t <= 1) {
particles.position = curveGeometry.getPointAt(t)
t += 0.005
} else {
t = 0;
}
requestAnimationFrame( animate );
render();
}
function render() {
renderer.render( scene, camera );
}
Just a rough concept of how you can do it, using THREE.Geometry():
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 50);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0x404040);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var grid = new THREE.GridHelper(40, 40, "white", "gray");
grid.rotation.x = Math.PI * -0.5;
scene.add(grid);
var curve = new THREE.EllipseCurve(0, 0, 20, 20, 0, Math.PI * 2, false, 0);
// using of .getPoints(division) will give you a set of points of division + 1
// so, let's get the points manually :)
var count = 10;
var inc = 1 / count;
var pointAt = 0;
var points = [];
for (let i = 0; i < count; i++) {
let point = curve.getPoint(pointAt); // get a point of THREE.Vector2()
point.z = 0; // geometry needs points of x, y, z; so add z
point.pointAt = pointAt; // save position along the curve in a custom property
points.push(point);
pointAt += inc; // increment position along the curve for next point
}
var pointsGeom = new THREE.Geometry();
pointsGeom.vertices = points;
console.log(points);
var pointsObj = new THREE.Points(pointsGeom, new THREE.PointsMaterial({
size: 1,
color: "aqua"
}));
scene.add(pointsObj);
var clock = new THREE.Clock();
var time = 0;
render();
function render() {
requestAnimationFrame(render);
time = clock.getDelta();
points.forEach(p => {
p.pointAt = (p.pointAt + time * 0.1) % 1; // it always will be from 0 to 1
curve.getPoint(p.pointAt, p); //re-using of the current point
});
pointsGeom.verticesNeedUpdate = true;
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

THREE.js , texture not displayed on a sprite

I'm trying to modify the canves/lines.js example and to display textured sprites instead of dots. I load a texture from a PNG file and I have replaced regular dots with sprites. They do not show.
function init() {
var container, separation = 100, amountX = 50, amountY = 50,
particles, particle,sprite;
var crateTexture = THREE.ImageUtils.loadTexture( 'images/crate.png' );
var crateMaterial = new THREE.SpriteMaterial( { map: crateTexture, useScreenCoordinates: false, color: 0xff0000 } );
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 100;
scene = new THREE.Scene();
renderer = new THREE.CanvasRenderer();
renderer.setClearColor (0x5bb0d2, 1);
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
// particles
var PI2 = Math.PI * 2;
var material = new THREE.SpriteCanvasMaterial( {
color: 0xfffffff,
program: function ( context ) {
context.beginPath();
context.arc( 0, 0, 0.5, 0, PI2, true );
context.fill();
}
} );
var geometry = new THREE.Geometry();
var x,y,z=0;
for ( var i = 0; i < 100; i ++ ) {
particle = new THREE.Sprite( crateMaterial );
particle.position.x = Math.random() * 2 - 1;
particle.position.y = Math.random() * 2 - 1;
particle.position.z = Math.random() * 2 - 1;
particle.position.normalize();
particle.position.multiplyScalar( Math.random() * 10 + 450 );
particle.scale.x = particle.scale.y = 64;
scene.add( particle );
}
what am I doing wrong ?
All I did was correct, there is an issue when reading textures from local computer. The solution is to assign proper privilages to web serwer OR to allow Chrome or other browser to read local files.
in case of Chrome, you can create a shortcut such as this:
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" chrome --allow-file-access-from-files

Three Js Object3D Button Group Detect Single Object Click While Mouse Movement Causes Object3D Button Group Zoomi

I am trying to detect a cube click in an Object3D group of Cubes. I have viewed, and tried to incorporate the examples and tutorials found at:
http://mrdoob.github.com/three.js/examples/webgl_interactive_cubes.html
and
http://mrdoob.github.com/three.js/examples/canvas_interactive_cubes.html
Also, I have consulted the posts on this site at:
Three.js - how to detect what shape was selected? after drag
and
how to Get CLICKED element in THREE.js
But for some reason, it's still not working. Can anyone please tell me what I'm doing wrong?
Here is my code, thanks:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Double Stamp It No Erasies</title>
<style>
html {
background: url(Images/ComicBookExplosionBackground.jpg) no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
body {
}
</style>
<script src="ThreeJs/build/three.min.js"></script>
</head>
<body onLoad="onLoad();" style="">
<div id="container" style="width:100%; height:100%; position:absolute;"></div>
<script>
var container, ButtonsCamera, ButtonsScene, ButtonsRenderer, ButtonsGeometry, ButtonsGroup;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
/****************************** CLICK START **********************************/
var mouse = { x: 0, y: 0 }, projector, INTERSECTED;
var objects = [];
/****************************** CLICK END **********************************/
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
//document.addEventListener( 'mousedown', onDocumentMouseDown, false );
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
ButtonsCamera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 10000 );
ButtonsCamera.position.z = 500;
ButtonsScene = new THREE.Scene();
ButtonsScene.fog = new THREE.Fog( 0xffffff, 1, 10000 );
/*************************** STACKOVERFLOW 1ST ANSWER START **********************************/
var ButtonsGeometry = new THREE.CubeGeometry( 100, 100, 100 );
var ButtonsMaterial = new THREE.MeshFaceMaterial( [
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } ),
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } ),
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } ),
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } ),
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } ),
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } )] );
/*************************** STACKOVERFLOW 1ST ANSWER END **********************************/
ButtonsGroup = new THREE.Object3D();
for ( var i = 0; i < 100; i ++ ) {
var ButtonsMesh;
if(i == 0)
{
ButtonsMesh = new THREE.Mesh( ButtonsGeometry, ButtonsMaterial );
}
else
{
ButtonsMesh = new THREE.Mesh( ButtonsGeometry, ButtonsMaterial );
}
ButtonsMesh.position.x = Math.random() * 2000 - 1000;
ButtonsMesh.position.y = Math.random() * 2000 - 1000;
ButtonsMesh.position.z = Math.random() * 2000 - 1000;
ButtonsMesh.rotation.x = Math.random() * 360 * ( Math.PI / 180 );
ButtonsMesh.rotation.y = Math.random() * 360 * ( Math.PI / 180 );
ButtonsMesh.matrixAutoUpdate = false;
ButtonsMesh.updateMatrix();
ButtonsGroup.add( ButtonsMesh );
}
ButtonsScene.add( ButtonsGroup );
/****************************** CLICK START **********************************/
objects.push( ButtonsMesh );
projector = new THREE.Projector();
/****************************** CLICK END **********************************/
ButtonsRenderer = new THREE.WebGLRenderer();
ButtonsRenderer.setSize( window.innerWidth, window.innerHeight );
ButtonsRenderer.sortObjects = false;
container.appendChild( ButtonsRenderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
/****************************** CLICK START **********************************/
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
/****************************** CLICK END **********************************/
}
/****************************** CLICK START **********************************/
function onDocumentMouseDown( event ) {
//alert('clicky');
event.preventDefault();
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
projector.unprojectVector( vector, ButtonsCamera );
var raycaster = new THREE.Raycaster( ButtonsCamera.position, vector.subSelf( ButtonsCamera.position ).normalize() );
var intersects = raycaster.intersectObjects( objects);
if ( intersects.length > 0 ) {
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
var particle = new THREE.Particle( particleMaterial );
particle.position = intersects[ 0 ].point;
particle.scale.x = particle.scale.y = 8;
ButtonsScene.add( particle );
}
/*
// Parse all the faces
for ( var i in intersects ) {
intersects[ i ].face.material[ 0 ].color.setHex( Math.random() * 0xffffff | 0x80000000 );
}
*/
}
/****************************** CLICK END **********************************/
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
ButtonsCamera.aspect = window.innerWidth / window.innerHeight;
ButtonsCamera.updateProjectionMatrix();
ButtonsRenderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseMove(event) {
mouseX = ( event.clientX - windowHalfX ) * 10;
mouseY = ( event.clientY - windowHalfY ) * 10;
}
function animate() {
requestAnimationFrame( animate );
render();
ButtonsStats.update();
}
/****************************** CLICK START **********************************/
var radius = 100;
var theta = 0;
/****************************** CLICK END **********************************/
function render() {
var ButtonsTime = Date.now() * 0.001;
var rx = Math.sin( ButtonsTime * 0.7 ) * 0.5,
ry = Math.sin( ButtonsTime * 0.3 ) * 0.5,
rz = Math.sin( ButtonsTime * 0.2 ) * 0.5;
ButtonsCamera.position.x += ( mouseX - ButtonsCamera.position.x ) * .05;
ButtonsCamera.position.y += ( - mouseY - ButtonsCamera.position.y ) * .05;
ButtonsCamera.lookAt( ButtonsScene.position );
ButtonsGroup.rotation.x = rx;
ButtonsGroup.rotation.y = ry;
ButtonsGroup.rotation.z = rz;
ButtonsRenderer.render( ButtonsScene, ButtonsCamera );
}
</script>
</body>
</html>
Hey I hope I am not too late but anyway the solution to your problem is
a misplaced statement and a deprecated method.
You only have one object in your objects array that is why when you click on a
random box the raycaster is unlikely to detect an intersection.
Move the array push call into the for loop in order to add every object into
the array instead of the last object created.
for (var i = 0; i < 100; i++) {
var ButtonsMesh;
if (i == 0)
{
ButtonsMesh = new THREE.Mesh(ButtonsGeometry, ButtonsMaterial);
}
else
{
ButtonsMesh = new THREE.Mesh(ButtonsGeometry, ButtonsMaterial);
}
ButtonsMesh.position.x = Math.random() * 2000 - 1000;
ButtonsMesh.position.y = Math.random() * 2000 - 1000;
ButtonsMesh.position.z = Math.random() * 2000 - 1000;
ButtonsMesh.rotation.x = Math.random() * 360 * (Math.PI / 180);
ButtonsMesh.rotation.y = Math.random() * 360 * (Math.PI / 180);
ButtonsMesh.matrixAutoUpdate = false;
ButtonsMesh.updateMatrix();
ButtonsGroup.add(ButtonsMesh);
objects.push(ButtonsMesh);
}
The second problem is that newer versions of THREE don't use subSelf(),
it is replaced by sub(). So make the change to the Raycaster definition.
var raycaster = new THREE.Raycaster(ButtonsCamera.position,
vector.sub(ButtonsCamera.position).normalize());
That should solve your problems but there are more errors in your code
but they are all trivial.
I hope this helps and here is a working version: http://jsbin.com/uhihoq/1/edit
Peace!

Resources