patch for the terrain editor slope tool from shirkit
git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9588 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
This commit is contained in:
parent
e9af2e4dc4
commit
8fa651fe43
@ -119,12 +119,12 @@ CreateTerrainVisualPanel2.smoothIterationsLabel.text=Rough
|
||||
CreateTerrainVisualPanel2.jLabel6.text=Smooth
|
||||
CreateTerrainVisualPanel2.jLabel7.text=Height Scale:
|
||||
CreateTerrainVisualPanel2.heightScale.text=1
|
||||
TerrainEditorTopComponent.toolHint.shirkit=Right click to set the markers position. A simple right click set the first marker, holding ctrl sets the second marker. Hold the Left button to apply the slope in the area you are hovering.
|
||||
TerrainEditorTopComponent.toolHint.slope=Right click to set the markers position. A simple right click set the first marker, holding ctrl sets the second marker. Hold the Left button to apply the slope in the area you are hovering.
|
||||
TerrainEditorTopComponent.jLabel6.text=
|
||||
TerrainEditorTopComponent.jLabel7.text=
|
||||
TerrainEditorTopComponent.levelPrecisionCheckbox.text=Precision
|
||||
TerrainEditorTopComponent.levelAbsoluteCheckbox.text=Absolute
|
||||
TerrainEditorTopComponent.levelAbsoluteHeightField.text=0
|
||||
TerrainEditorTopComponent.slopePrecisionCheckbox.text=Precision
|
||||
TerrainEditorTopComponent.slopeTerrainButton.toolTipText=Slope terrain
|
||||
TerrainEditorTopComponent.slopeTerrainButton.text=
|
||||
TerrainEditorTopComponent.slopeLockCheckbox.text=Lock
|
||||
|
@ -188,12 +188,15 @@
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="slopePrecisionCheckboxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="jLabel7">
|
||||
<Component class="javax.swing.JCheckBox" name="slopeLockCheckbox">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="com/jme3/gde/terraineditor/Bundle.properties" key="TerrainEditorTopComponent.jLabel7.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="com/jme3/gde/terraineditor/Bundle.properties" key="TerrainEditorTopComponent.slopeLockCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="slopeLockCheckboxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
@ -548,7 +551,7 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="147" max="32767" attributes="0"/>
|
||||
<EmptySpace min="0" pref="148" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
|
@ -239,7 +239,7 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
|
||||
scaleField = new javax.swing.JTextField();
|
||||
slopeBrushPanel = new javax.swing.JPanel();
|
||||
slopePrecisionCheckbox = new javax.swing.JCheckBox();
|
||||
jLabel7 = new javax.swing.JLabel();
|
||||
slopeLockCheckbox = new javax.swing.JCheckBox();
|
||||
jToolBar1 = new javax.swing.JToolBar();
|
||||
createTerrainButton = new javax.swing.JButton();
|
||||
jSeparator1 = new javax.swing.JToolBar.Separator();
|
||||
@ -381,8 +381,13 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
|
||||
});
|
||||
slopeBrushPanel.add(slopePrecisionCheckbox);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(jLabel7, org.openide.util.NbBundle.getMessage(TerrainEditorTopComponent.class, "TerrainEditorTopComponent.jLabel7.text")); // NOI18N
|
||||
slopeBrushPanel.add(jLabel7);
|
||||
org.openide.awt.Mnemonics.setLocalizedText(slopeLockCheckbox, org.openide.util.NbBundle.getMessage(TerrainEditorTopComponent.class, "TerrainEditorTopComponent.slopeLockCheckbox.text")); // NOI18N
|
||||
slopeLockCheckbox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
slopeLockCheckboxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
slopeBrushPanel.add(slopeLockCheckbox);
|
||||
|
||||
setBackground(java.awt.Color.gray);
|
||||
|
||||
@ -572,7 +577,7 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
|
||||
jPanel3.setLayout(jPanel3Layout);
|
||||
jPanel3Layout.setHorizontalGroup(
|
||||
jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 147, Short.MAX_VALUE)
|
||||
.addGap(0, 148, Short.MAX_VALUE)
|
||||
);
|
||||
jPanel3Layout.setVerticalGroup(
|
||||
jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
@ -953,6 +958,10 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
|
||||
updateSlopeToolParams();
|
||||
}//GEN-LAST:event_slopePrecisionCheckboxActionPerformed
|
||||
|
||||
private void slopeLockCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_slopeLockCheckboxActionPerformed
|
||||
updateSlopeToolParams();
|
||||
}//GEN-LAST:event_slopeLockCheckboxActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton addTextureButton;
|
||||
private javax.swing.JButton createTerrainButton;
|
||||
@ -966,7 +975,6 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
|
||||
private javax.swing.JLabel jLabel3;
|
||||
private javax.swing.JLabel jLabel4;
|
||||
private javax.swing.JLabel jLabel6;
|
||||
private javax.swing.JLabel jLabel7;
|
||||
private javax.swing.JPanel jPanel2;
|
||||
private javax.swing.JPanel jPanel3;
|
||||
private javax.swing.JScrollPane jScrollPane1;
|
||||
@ -996,6 +1004,7 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
|
||||
private javax.swing.JLabel scaleLabel;
|
||||
private javax.swing.JTextField shininessField;
|
||||
private javax.swing.JPanel slopeBrushPanel;
|
||||
private javax.swing.JCheckBox slopeLockCheckbox;
|
||||
private javax.swing.JCheckBox slopePrecisionCheckbox;
|
||||
private javax.swing.JToggleButton slopeTerrainButton;
|
||||
private javax.swing.JToggleButton smoothTerrainButton;
|
||||
@ -1048,6 +1057,7 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
|
||||
private void updateSlopeToolParams() {
|
||||
SlopeExtraToolParams params = new SlopeExtraToolParams();
|
||||
params.precision = slopePrecisionCheckbox.isSelected();
|
||||
params.lock = slopeLockCheckbox.isSelected();
|
||||
toolController.setExtraToolParams(params);
|
||||
}
|
||||
|
||||
|
@ -40,5 +40,6 @@ import com.jme3.gde.terraineditor.ExtraToolParams;
|
||||
public class SlopeExtraToolParams implements ExtraToolParams {
|
||||
|
||||
public boolean precision;
|
||||
public boolean lock;
|
||||
|
||||
}
|
||||
|
@ -32,32 +32,39 @@
|
||||
package com.jme3.gde.terraineditor.tools;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
|
||||
import com.jme3.gde.terraineditor.ExtraToolParams;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.event.KeyInputEvent;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.BillboardControl;
|
||||
import com.jme3.scene.shape.Line;
|
||||
import com.jme3.scene.shape.Sphere;
|
||||
import org.openide.loaders.DataObject;
|
||||
|
||||
/**
|
||||
* Generates a slope between two control points.
|
||||
*
|
||||
* @author Shirkit
|
||||
*/
|
||||
public class SlopeTerrainTool extends TerrainTool {
|
||||
|
||||
private Vector3f point1, point2;
|
||||
private Geometry markerThird;
|
||||
private Geometry markerThird, line;
|
||||
private Node parent;
|
||||
private SlopeExtraToolParams toolParams;
|
||||
private BitmapText angleText;
|
||||
private boolean leftCtrl = false;
|
||||
|
||||
public SlopeTerrainTool() {
|
||||
toolHintTextKey = "TerrainEditorTopComponent.toolHint.shirkit";
|
||||
toolHintTextKey = "TerrainEditorTopComponent.toolHint.slope";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,17 +72,20 @@ public class SlopeTerrainTool extends TerrainTool {
|
||||
super.activate(manager, parent);
|
||||
addMarkerSecondary(parent);
|
||||
addMarkerThird(parent);
|
||||
addLineAndText();
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideMarkers() {
|
||||
super.hideMarkers();
|
||||
if (markerThird != null) {
|
||||
if (markerThird != null)
|
||||
markerThird.removeFromParent();
|
||||
}
|
||||
|
||||
line.removeFromParent();
|
||||
angleText.removeFromParent();
|
||||
}
|
||||
|
||||
|
||||
private void addMarkerThird(Node parent) {
|
||||
if (markerThird == null) {
|
||||
markerThird = new Geometry("edit marker secondary");
|
||||
@ -90,20 +100,29 @@ public class SlopeTerrainTool extends TerrainTool {
|
||||
parent.attachChild(markerThird);
|
||||
}
|
||||
|
||||
private void addLineAndText() {
|
||||
line = new Geometry("line", new Line(Vector3f.ZERO, Vector3f.ZERO));
|
||||
Material m = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
m.setColor("Color", ColorRGBA.White);
|
||||
line.setMaterial(m);
|
||||
|
||||
angleText = new BitmapText(manager.loadFont("Interface/Fonts/Default.fnt"));
|
||||
BillboardControl control = new BillboardControl();
|
||||
angleText.addControl(control);
|
||||
angleText.setSize(0.5f);
|
||||
angleText.setCullHint(Spatial.CullHint.Never);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPrimary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
|
||||
if (point1 != null && point2 != null) {
|
||||
SlopeTerrainToolAction action = new SlopeTerrainToolAction(point, point1, point2, radius, weight, toolParams.precision);
|
||||
if (point1 != null && point2 != null && point1.distance(point2) > 0.01f) { // Preventing unexpected behavior, like destroying the terrain
|
||||
SlopeTerrainToolAction action = new SlopeTerrainToolAction(point, point1, point2, radius, weight, toolParams.precision, toolParams.lock);
|
||||
action.actionPerformed(rootNode, dataObject);
|
||||
}
|
||||
}
|
||||
private boolean leftCtrl = false;
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyInputEvent kie) {
|
||||
if (kie.getKeyCode() == KeyInput.KEY_LCONTROL) {
|
||||
leftCtrl = kie.isPressed();
|
||||
}
|
||||
switch (kie.getKeyCode()) {
|
||||
case KeyInput.KEY_LCONTROL:
|
||||
leftCtrl = kie.isPressed();
|
||||
@ -113,25 +132,67 @@ public class SlopeTerrainTool extends TerrainTool {
|
||||
point2 = null;
|
||||
markerSecondary.removeFromParent();
|
||||
markerThird.removeFromParent();
|
||||
line.removeFromParent();
|
||||
angleText.removeFromParent();
|
||||
break;
|
||||
case KeyInput.KEY_UP:
|
||||
markerThird.move(0f, 0.1f, 0f);
|
||||
point2.set(markerThird.getLocalTranslation());
|
||||
updateAngle();
|
||||
break;
|
||||
case KeyInput.KEY_DOWN:
|
||||
markerThird.move(0f, -0.1f, 0f);
|
||||
point2.set(markerThird.getLocalTranslation());
|
||||
updateAngle();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAngle() {
|
||||
Vector3f temp, higher, lower;
|
||||
if (point2.y > point1.y) {
|
||||
temp = point2;
|
||||
higher = point2;
|
||||
lower = point1;
|
||||
} else {
|
||||
temp = point1;
|
||||
higher = point1;
|
||||
lower = point2;
|
||||
}
|
||||
temp = temp.clone().setY(lower.y);
|
||||
|
||||
float angle = ((FastMath.asin(temp.distance(higher) / lower.distance(higher))) * FastMath.RAD_TO_DEG);
|
||||
|
||||
angleText.setText(angle + " degrees");
|
||||
angleText.setLocalTranslation(new Vector3f().interpolate(point1, point2, 0.5f));
|
||||
|
||||
if (line.getParent() == null) {
|
||||
parent.attachChild(line);
|
||||
parent.attachChild(angleText);
|
||||
}
|
||||
((Line) line.getMesh()).updatePoints(point1, point2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionSecondary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
|
||||
if (leftCtrl) {
|
||||
point2 = point;
|
||||
if (markerThird.getParent() == null) {
|
||||
if (markerThird.getParent() == null)
|
||||
parent.attachChild(markerThird);
|
||||
}
|
||||
|
||||
markerThird.setLocalTranslation(point);
|
||||
} else {
|
||||
point1 = point;
|
||||
if (markerSecondary.getParent() == null) {
|
||||
if (markerSecondary.getParent() == null)
|
||||
parent.attachChild(markerSecondary);
|
||||
}
|
||||
|
||||
markerSecondary.setLocalTranslation(point);
|
||||
}
|
||||
if (point1 != null && point2 != null)
|
||||
updateAngle();
|
||||
else
|
||||
if (line != null)
|
||||
line.removeFromParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -139,6 +200,4 @@ public class SlopeTerrainTool extends TerrainTool {
|
||||
if (params instanceof SlopeExtraToolParams)
|
||||
this.toolParams = (SlopeExtraToolParams) params;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -32,7 +32,9 @@
|
||||
package com.jme3.gde.terraineditor.tools;
|
||||
|
||||
import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.math.Plane;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.terrain.Terrain;
|
||||
import java.util.ArrayList;
|
||||
@ -52,41 +54,43 @@ public class SlopeTerrainToolAction extends AbstractTerrainToolAction {
|
||||
private List<Vector2f> undoLocs;
|
||||
private List<Float> undoHeights;
|
||||
private final boolean precise;
|
||||
private final boolean lock;
|
||||
|
||||
public SlopeTerrainToolAction(Vector3f current, Vector3f point1, Vector3f point2, float radius, float weight, boolean precise) {
|
||||
public SlopeTerrainToolAction(Vector3f current, Vector3f point1, Vector3f point2, float radius, float weight, boolean precise, boolean lock) {
|
||||
this.current = current.clone();
|
||||
this.point1 = point1;
|
||||
this.point2 = point2;
|
||||
this.radius = radius;
|
||||
this.weight = weight;
|
||||
this.precise = precise;
|
||||
this.lock = lock;
|
||||
name = "Slope terrain";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doApplyTool(AbstractSceneExplorerNode rootNode) {
|
||||
Terrain terrain = getTerrain(rootNode.getLookup().lookup(Node.class));
|
||||
if (terrain == null) {
|
||||
if (terrain == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
modifyHeight(terrain, point1, point2, current, radius, weight, precise);
|
||||
modifyHeight(terrain, point1, point2, current, radius, weight, precise, lock);
|
||||
|
||||
return terrain;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doUndoTool(AbstractSceneExplorerNode rootNode, Object undoObject) {
|
||||
if (undoObject == null) {
|
||||
if (undoObject == null)
|
||||
return;
|
||||
}
|
||||
if (undoLocs == null || undoHeights == null) {
|
||||
|
||||
if (undoLocs == null || undoHeights == null)
|
||||
return;
|
||||
}
|
||||
|
||||
resetHeight((Terrain) undoObject, undoLocs, undoHeights, precise);
|
||||
}
|
||||
|
||||
private void modifyHeight(Terrain terrain, Vector3f point1, Vector3f point2, Vector3f current, float radius, float weight, boolean precise) {
|
||||
private void modifyHeight(Terrain terrain, Vector3f point1, Vector3f point2, Vector3f current, float radius, float weight, boolean precise, boolean lock) {
|
||||
// Make sure we go for the right direction, or we could be creating a slope to the oposite side
|
||||
if (point1.y > point2.y) {
|
||||
Vector3f temp = point1;
|
||||
point1 = point2;
|
||||
@ -104,55 +108,60 @@ public class SlopeTerrainToolAction extends AbstractTerrainToolAction {
|
||||
List<Vector2f> locs = new ArrayList<Vector2f>();
|
||||
List<Float> heights = new ArrayList<Float>();
|
||||
undoHeights = new ArrayList<Float>();
|
||||
|
||||
for (int z = -radiusStepsZ; z < radiusStepsZ; z++) {
|
||||
|
||||
Plane p1 = new Plane();
|
||||
Plane p2 = new Plane();
|
||||
p1.setOriginNormal(point1, point1.subtract(point2).normalize());
|
||||
p2.setOriginNormal(point2, point1.subtract(point2).normalize());
|
||||
|
||||
for (int z = -radiusStepsZ; z < radiusStepsZ; z++)
|
||||
for (int x = -radiusStepsZ; x < radiusStepsX; x++) {
|
||||
|
||||
float locX = current.x + (x * xStepAmount);
|
||||
float locZ = current.z + (z * zStepAmount);
|
||||
|
||||
// see if it is in the radius of the tool
|
||||
if (ToolUtils.isInRadius(locX - current.x, locZ - current.z, radius)) {
|
||||
|
||||
|
||||
if (ToolUtils.isInRadius(locX - current.x, locZ - current.z, radius)) { // see if it is in the radius of the tool
|
||||
Vector2f terrainLoc = new Vector2f(locX, locZ);
|
||||
|
||||
// adjust height based on radius of the tool
|
||||
float terrainHeightAtLoc = terrain.getHeightmapHeight(terrainLoc) * ((Node) terrain).getWorldScale().y;
|
||||
float radiusWeight = ToolUtils.calculateRadiusPercent(radius, locX - current.x, locZ - current.z);
|
||||
|
||||
float point1Distance = point1.distance(new Vector3f(locX, terrainHeightAtLoc, locZ));
|
||||
float desiredHeight = point1.y + (point2.y - point1.y) * (point1Distance / totaldistance);
|
||||
|
||||
if (!precise) {
|
||||
float epsilon = 0.1f * weight; // rounding error for snapping
|
||||
if (!lock || (lock && p1.whichSide(new Vector3f(locX, 0f, locZ)) != p2.whichSide(new Vector3f(locX, 0f, locZ))))
|
||||
if (!precise) {
|
||||
float epsilon = 0.1f * weight; // rounding error for snapping
|
||||
|
||||
float adj = 0;
|
||||
if (terrainHeightAtLoc < desiredHeight) {
|
||||
adj = 1;
|
||||
} else if (terrainHeightAtLoc > desiredHeight) {
|
||||
adj = -1;
|
||||
}
|
||||
float adj = 0;
|
||||
if (terrainHeightAtLoc < desiredHeight)
|
||||
adj = 1;
|
||||
else
|
||||
if (terrainHeightAtLoc > desiredHeight)
|
||||
adj = -1;
|
||||
|
||||
adj *= radiusWeight * weight;
|
||||
float radiusWeight = ToolUtils.calculateRadiusPercent(radius, locX - current.x, locZ - current.z);
|
||||
|
||||
// test if adjusting too far and then cap it
|
||||
if (adj > 0 && ToolUtils.floatGreaterThan((terrainHeightAtLoc + adj), desiredHeight, epsilon)) {
|
||||
adj = desiredHeight - terrainHeightAtLoc;
|
||||
} else if (adj < 0 && ToolUtils.floatLessThan((terrainHeightAtLoc + adj), desiredHeight, epsilon)) {
|
||||
adj = terrainHeightAtLoc - desiredHeight;
|
||||
}
|
||||
|
||||
if (!ToolUtils.floatEquals(adj, 0, 0.001f)) {
|
||||
adj *= radiusWeight * weight;
|
||||
|
||||
// test if adjusting too far and then cap it
|
||||
if (adj > 0 && ToolUtils.floatGreaterThan((terrainHeightAtLoc + adj), desiredHeight, epsilon))
|
||||
adj = desiredHeight - terrainHeightAtLoc;
|
||||
else
|
||||
if (adj < 0 && ToolUtils.floatLessThan((terrainHeightAtLoc + adj), desiredHeight, epsilon))
|
||||
adj = terrainHeightAtLoc - desiredHeight;
|
||||
|
||||
if (!ToolUtils.floatEquals(adj, 0, 0.001f)) {
|
||||
locs.add(terrainLoc);
|
||||
heights.add(adj);
|
||||
}
|
||||
} else {
|
||||
locs.add(terrainLoc);
|
||||
heights.add(adj);
|
||||
heights.add(desiredHeight);
|
||||
undoHeights.add(terrainHeightAtLoc);
|
||||
}
|
||||
} else {
|
||||
locs.add(terrainLoc);
|
||||
heights.add(desiredHeight);
|
||||
undoHeights.add(terrainHeightAtLoc);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
undoLocs = locs;
|
||||
if (!precise)
|
||||
undoHeights = heights;
|
||||
|
Loading…
x
Reference in New Issue
Block a user