Are there any examples of pedestrian modelling in repast simphony? - simulation

Are there any examples of pedestrian modelling in repast simphony? I am novice in repast and was trying to model a simple pedestrian movement simulation. Any pointers to useful resources/ examples?

Andrew Crook's blog GIS and Agent-Based Modeling (http://www.gisagents.org/) has lots of interesting links to pedestrian models. I think there are even some specific to Repast.

Repast isn't the best for open libraries, but I've had some luck searching GitHub. Here's a basic ped agent I built once, you'll have to build a context with a scheduler class to call the pedestrians:
context:
public class RoadBuilder extends DefaultContext<Object> implements ContextBuilder<Object> {
context.setId("driving1");
ContinuousSpaceFactory spaceFactory =
ContinuousSpaceFactoryFinder.createContinuousSpaceFactory(null);
ContinuousSpace<Object> space =
spaceFactory.createContinuousSpace("space",context, new SimpleCartesianAdder<Object>(),
new StrictBorders(), roadL, worldW);
clock = RunEnvironment.getInstance().getCurrentSchedule();
flowSource = new Scheduler();
context.add(flowSource);
return context;
}
the scheduler:
public class Scheduler {
static ArrayList<Ped> allPeds;
#ScheduledMethod(start = 1, interval = 1, priority = 1)
public void doStuff() {
Ped addedPed = addPed(1);
allPeds.add(addedPed);
for (Ped a : allPeds) {
a.calc();}
for (Ped b : allPeds) {
b.walk();}
public Ped addPed(int direction) {
Context<Object> context = ContextUtils.getContext(this);
ContinuousSpace<Object> space = (ContinuousSpace<Object>) context.getProjection("space");
Ped newPed = new Ped(space,direction);
context.add(newPed);
space.moveTo(newPed,xPlacement,yPlacement);
newPed.myLoc = space.getLocation(newPed);
return(newPed);
}
The pedestrians - This is based on a "generalized force model" (source: Simulating Dynamical Features of Escape Panic - Helbing, Farkas, and Vicsek - https://arxiv.org/pdf/cond-mat/0009448.pdf)
and here's the pedestrian class
public class Ped {
private ContinuousSpace<Object> space;
private List<Double> forcesX, forcesY;
private NdPoint endPt;
private Random rnd = new Random();
private int age;
private double endPtDist, endPtTheta, critGap;
private double side = RoadBuilder.sidewalk;
private double wS, etaS, wV, etaV, sigR; //errors
private double m, horiz, A, B, k, r; //interactive force constants (accT is also)
public NdPoint myLoc, destination;
public double[] v, dv, newV;
public double xTime, accT, maxV, xLoc, yLoc;
public int dir; // dir = 1 walks up, -1 walks down
public void calc() {
myLoc = space.getLocation(this);
dv = accel(myLoc,dir,destination);
newV = sumV(v,dv);
newV = limitV(newV);
}
public void walk() {
v = newV;
move(myLoc,v);
}
public double[] accel(NdPoint location, int direct, NdPoint endPt) {
forcesX = new ArrayList<Double>();
forcesY = new ArrayList<Double>();
double xF, yF;
double[] acc;
xF = yF = 0;
//calculate heading to endpoint
endPtDist = space.getDistance(location, endPt);
double endPtDelX = endPt.getX()-location.getX();
endPtTheta = FastMath.asin((double)direct*endPtDelX/endPtDist);
if (direct == -1) {
endPtTheta += Math.PI;}
//calculate motive force
Double motFx = (maxV*Math.sin(endPtTheta) - v[0])/accT;
Double motFy = (maxV*Math.cos(endPtTheta) - v[1])/accT;
forcesX.add(motFx);
forcesY.add(motFy);
//calculate interactive forces
//TODO: write code to make a threshold for interaction instead of the arbitrary horizon
for (Ped a : Scheduler.allPeds) {
if (a != this) {
NdPoint otherLoc = space.getLocation(a);
double otherY = otherLoc.getY();
double visible = Math.signum((double)dir*(otherY-yLoc));
if (visible == 1) { //peds only affected by those in front of them
double absDist = space.getDistance(location, otherLoc);
if (absDist < horiz) {
double delX = location.getX()-otherLoc.getX();
double delY = location.getY()-otherLoc.getY();
double delXabs = Math.abs(delX);
double signFx = Math.signum(delX);
double signFy = Math.signum(delY);
double theta = FastMath.asin(delXabs/absDist);
double rij = r + a.r;
Double interFx = signFx*A*Math.exp((rij-absDist)/B)*Math.sin(theta)/m;
Double interFy = signFy*A*Math.exp((rij-absDist)/B)*Math.cos(theta)/m;
forcesX.add(interFx);
forcesY.add(interFy);}}}}
//sum all forces
for (Double b : forcesX) {
xF += b;}
for (Double c : forcesY) {
yF += c;}
acc = new double[] {xF, yF};
return acc;
}
public void move(NdPoint loc, double[] displacement) {
double[] zero = new double[] {0,0};
double yl = loc.getY();
if (displacement != zero) {
space.moveByDisplacement(this,displacement);
myLoc = space.getLocation(this);}
}
public double[] limitV(double[] input) {
double totalV, norm;
if (this.dir == 1) {
if (input[1] < 0) {
input[1] = 0;}}
else {
if (input[1] > 0) {
input[1] = 0;}}
totalV = Math.sqrt(input[0]*input[0] + input[1]*input[1]);
if (totalV > maxV) {
norm = maxV/totalV;
input[0] = input[0]*norm;
input[1] = input[1]*norm;}
return input;
}
public double[] sumV(double[] a, double[] b) {
double[] c = new double[2];
for (int i = 0; i < 2; i++) {
c[i] = a[i] + b[i];}
return c;
}
public Ped(ContinuousSpace<Object> contextSpace, int direction) {
space = contextSpace;
maxV = rnd.nextGaussian() * UserPanel.pedVsd + UserPanel.pedVavg;
dir = direction; // 1 moves up, -1 moves down
v = new double[] {0,(double)dir*.5*maxV};
age = 0;
//3-circle variables - from Helbing, et al (2000) [r from Rouphail et al 1998]
accT = 0.5/UserPanel.tStep; //acceleration time
m = 80; //avg ped mass in kg
horiz = 5/RoadBuilder.spaceScale; //distance at which peds affect each other
A = 2000*UserPanel.tStep*UserPanel.tStep/RoadBuilder.spaceScale; //ped interaction constant (kg*space units/time units^2)
B = 0.08/RoadBuilder.spaceScale; //ped distance interaction constant (space units)
k = 120000*UserPanel.tStep*UserPanel.tStep; //wall force constant
r = 0.275/RoadBuilder.spaceScale; //ped radius (space units)
}
}

Related

Microphone, gain value and spectrum values do not sync using Unity

I am making a simple voice visualization program. My goals are:
Playback microphone input
Visualize voice spectrum and gain in real time
Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VisualizeVoice : MonoBehaviour
{
private const int NUM_SPECTRUM_SAMPLES = 256;
private const int NUM_SPECTRUM_BARS = 32;
private const int NUM_PCM_SAMPLES = 16000;
private const float BAR_DROP_SPEED = 1e-3f;
private const int NUM_SAMPLES_TO_AVERAGE = 8;
private string _deviceName;
private float[] _spectrumData = new float[NUM_SPECTRUM_SAMPLES];
private float[] _fPCMData = new float[NUM_PCM_SAMPLES];
private float _gain = 0;
private AudioClip _audio; // Audio from microphone
private AudioSource _playback; // To play the audio from microphone
// For visualization
private GameObject[] _spectrumBars = new GameObject[NUM_SPECTRUM_BARS];
private GameObject _gainBar;
// Start is called before the first frame update
void Start()
{
if (Microphone.devices.Length == 0) {
Debug.LogError("No Microphone");
return;
}
_deviceName = Microphone.devices[0];
Debug.Log("Current microphone is " + _deviceName);
if ((_playback = this.GetComponent<AudioSource>()) == null) {
_playback = this.gameObject.AddComponent<AudioSource>();
}
_playback.loop = true;
_playback.bypassEffects = true;
_playback.bypassListenerEffects = true;
_playback.bypassReverbZones = true;
_playback.priority = 0;
_playback.pitch = 1;
_playback.clip = _audio = Microphone.Start(_deviceName, true, 1, AudioSettings.outputSampleRate);
// Sync microphone and playback, but it always fails
float waitTime = 0;
while (!(Microphone.GetPosition(_deviceName) > 0) && waitTime <= 2)
waitTime += Time.deltaTime;
if (waitTime > 2) {
Debug.LogError("time out waiting for microphone");
}
_playback.Play();
InitVisualization();
}
// Update is called once per frame
void Update()
{
// Get PCM data and calculate gain
var audioPosition = Microphone.GetPosition(_deviceName);
_audio.GetData(_fPCMData, audioPosition);
UpdateGain();
// Get spectrum data
_playback.GetSpectrumData(_spectrumData, 0, FFTWindow.BlackmanHarris);
// Update visualization
UpdateVisualization();
}
private void InitVisualization()
{
// Initialize spectrum bars
for (int ibar = 0; ibar < NUM_SPECTRUM_BARS; ibar++) {
_spectrumBars[ibar] = GameObject.CreatePrimitive(PrimitiveType.Cube);
_spectrumBars[ibar].transform.parent = this.transform;
_spectrumBars[ibar].transform.localPosition = new Vector3(ibar, 0, 0);
_spectrumBars[ibar].transform.localScale = new Vector3(1, 0, 1);
}
// Initialize gain bar
_gainBar = GameObject.CreatePrimitive(PrimitiveType.Cube);
_gainBar.transform.parent = this.transform;
_gainBar.transform.localPosition = new Vector3(-5, 0, 0);
_gainBar.transform.localScale = new Vector3(4, 0, 1);
// Overall dimension
this.transform.localScale = new Vector3(0.2f, 10.0f, 0.2f);
}
private void UpdateVisualization()
{
// Update spectrum bars
int nSamplesPerBar = NUM_SPECTRUM_SAMPLES / NUM_SPECTRUM_BARS;
for (int ibar = 0; ibar < NUM_SPECTRUM_BARS; ibar++) {
// Calculate value of each bar
float value = 0;
for (int isample = 0; isample < nSamplesPerBar; isample++) {
value += _spectrumData[ibar * nSamplesPerBar + isample];
}
value /= nSamplesPerBar;
// Use current value if increasing, or slowly drop previous value if decreasing
float prevValue = _spectrumBars[ibar].transform.localScale.y;
if (value < prevValue)
value = prevValue - BAR_DROP_SPEED;
// Y scale is set to value
_spectrumBars[ibar].transform.localScale = new Vector3(1, value, 1);
}
// Update gain bar
_gainBar.transform.localScale = new Vector3(4, _gain, 1);
}
private void UpdateGain()
{
_gain = 0;
for(int i = 0; i < NUM_SAMPLES_TO_AVERAGE; i++) {
_gain += Mathf.Abs(_fPCMData[NUM_PCM_SAMPLES - i - 1]);
}
_gain /= NUM_SAMPLES_TO_AVERAGE;
}
}
Here are my questions:
I can't use while (!Microphone.GetPosition(_deviceName) > 0)); to avoid latency from microphone to speaker. If I use it, my application just freezes. If I add code to allow time-out, it has time-out every time.
The gain bar seems irrelevant with my voice. I don't know if my calculation is right.
I'm not sure if I need to average over multiple samples calculating gains, and how many samples I need to average over. I need this gain value later to detect silent moments and cut audio data.
To 1.
You can. Unity allows to define Start as a Coroutine
private IEnumerator Start()
{
...
}
On this way you can use a non blocking
while (!Microphone.GetPosition(_deviceName) > 0))
{
yield return null;
}

DL4J linear regression

I am new in neural networks. I am trying to implement and train simple neural network with DL4j. My function:
y = x * 2 + 300
My vision
My result
Parameters:
public final int seed = 12345;
public final int iterations = 1;
public final int nEpochs = 1;
public final int batchSize = 1000;
public final double learningRate = 0.01;
public final Random rng = new Random(seed);
public final int numInputs = 2;
public final int numOutputs = 1;
public final double maxX = 100;//xmax = 100; ymax=500.
public final double scale = 500;//for scale out x and y.
Network configuration:
public MultiLayerConfiguration createConf() {
return new NeuralNetConfiguration.Builder()
.seed(seed)
.iterations(iterations)
.optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)
.learningRate(learningRate)
.weightInit(WeightInit.XAVIER)
.updater(new Nesterovs(0.9))
.list()
.layer(0, new OutputLayer.Builder(LossFunctions.LossFunction.MSE)
.activation(Activation.IDENTITY)
.nIn(numInputs).nOut(numOutputs).build())
.pretrain(false).backprop(true).build();
}
Training data:
public DataSetIterator generateTrainingData() {
List<DataSet> list = new ArrayList<>();
for (int i = 0; i < batchSize; i++) {
double x = rng.nextDouble() * maxX * (rng.nextBoolean() ? 1 : -1);
double y = y(x);
list.add(
new DataSet(
Nd4j.create(new double[]{x / scale, 1}),
Nd4j.create(new double[]{y / scale})
)
);
}
return new ListDataSetIterator(list, batchSize);
}
Testing:
public void test() {
final MultiLayerNetwork net = new MultiLayerNetwork(createConf());
net.init();
net.setListeners(new ScoreIterationListener(1));
for (int i = 0; i < nEpochs; i++) {
net.fit(generateTrainingData());
}
int idx = 0;
double x[] = new double[19];
double y[] = new double[19];
double p[] = new double[19];
for (double i = -90; i < 100; i += 10) {
x[idx] = i;
y[idx] = y(i);
p[idx] = scale * net.output(Nd4j.create(new double[]{i / scale, 1})).getDouble(0, 0);
idx++;
}
plot(x, y, p);
}
Please tell me what i am doing wrong or if i have incorrect vision...
Thank you in advance,
Regards,
Minas
Take a look at this example:
https://github.com/deeplearning4j/dl4j-examples/tree/master/dl4j-examples/src/main/java/org/deeplearning4j/examples/feedforward/regression
Few tips:
Use our built in normalization tools. Don't do this yourself.
Our normalization tools allow you to normalize labels as well.
Turn minibatch off (set minibatch(false) on the neural net config near the top)
Ultimately you still aren't actually doing "minibatch learning"
Also, you're regenerating the dataset each time. There's no need to do that. Just create it once and pass it in to fit.
For visualization purposes, use the restore mechanism I mentioned earlier (This is in the example, you can pick 1 of any of the normalizers like MinMaxScalar, NormalizeStandardize,.. etc)
Your iterations are also wrong. Just keep that value at 1 and keep your for loop. Otherwise you're just overfitting and spending way more of your training time then you need to. An "iteration" is actually the number of updates you want to run per fit call on the same dataset. Next release we are getting rid of that option anyways.

Dynamically adding tiles to a grid based map

I want to have an infinitely explorable map. The plan is to create categories of game tiles (roads, obstacles, buildings), and randomly choose a category of game tile to be added when the player approaches the edge of the existing set of tiles. Tiles will also be destroyed once the player is 2 grid squares away from that tile. Currently I am using a multidimensional array that requires a size initializer.
What I have so far:
public class GameManager : MonoBehaviour
{
private GameObject[,] tileArray;
public GameObject groundTile;
public GameObject player;
private int tileSize = 80;
private int nextFarX = 1;
private int nextFarZ = 1;
private int nextNearX = -1;
private int nextNearZ = -1;
private float padding = .1f;
private int arrayOffset;
private int arrayDimension;
// Use this for initialization
void Start ()
{
arrayDimension = 200;
arrayOffset = arrayDimension / 2;
tileArray = new GameObject[,];
this.AddCubeAt(0, 0);
}
// Update is called once per frame
void Update () {
var x = Convert.ToInt32(player.transform.position.x / tileSize);
var z = Convert.ToInt32(player.transform.position.z / tileSize);
for (int i = -1; i < 2; i++)
{
for (int j = -1; j < 2; j++)
{
var checkX = x + i;
var checkZ = z + j;
if (tileArray[checkX + arrayOffset, checkZ + arrayOffset] == null)
{
//player is less than 2 tiles away from this grid, add a tile
this.AddCubeAt(checkX, checkZ);
}
}
}
// feels like a hack, but it will remove tiles that are not touching the tile that the player occupies
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 6; j++)
{
if (i == 0 | i == 5 | j == 0 | j == 5)
{
if (tileArray[x + (i-2) + arrayOffset, z + (j-2) + arrayOffset] != null)
{
Destroy(tileArray[x + (i - 2) + arrayOffset, z + (j - 2) + arrayOffset]);
tileArray[x + (i - 2) + arrayOffset, z + (j - 2) + arrayOffset] = null;
}
}
}
}
}
private void AddCubeAt(int x, int z)
{
var pos = new Vector3(x * tileSize, 0, z * tileSize);
var rot = Quaternion.identity;
GameObject newCube = (GameObject)Instantiate(groundTile, pos, rot);
tileArray[x + arrayOffset, z + arrayOffset] = newCube;
}
}
What is a better way to approach this?
You should familiarize yourself with Graph Data Structure (not adjacency matrix implementation). It's much more appropriate for this task. And, I would solve this
Tiles will also be destroyed once the player is 2 grid squares away from that tile
in another way: Every time player changed his position I would start DFS on target depth (in your case it's 2) and remove found tiles.
Decided to go with a simple Dictionary and methods to query/update it:
private GameObject RetrieveTileAt(int x, int z)
{
string key = string.Format("{0}.{1}", x, z);
if (tileDictionary.ContainsKey(key))
{
return tileDictionary[key];
}
else
{
return null;
}
}
private void InsertTileAt(int x, int z, GameObject tile)
{
string key = string.Format("{0}.{1}", x, z);
tileDictionary[key] = tile;
}
It is not an infinitely sized grid, (int min + int max)squared, but it should be far more than I need.

Merge textures at Runtime

Is there any way to "bake" one texture to another, except for using SetPixels()?
Now i'm trying to use something like that, but it too slow:
public static Texture2D CombineTextures(Texture2D aBaseTexture, Texture2D aToCopyTexture, int x, int y)
{
int aWidth = aBaseTexture.width;
int aHeight = aBaseTexture.height;
int bWidth = aToCopyTexture.width;
int bHeight = aToCopyTexture.height;
Texture2D aReturnTexture = new Texture2D(aWidth, aHeight, TextureFormat.RGBA32, false);
Color[] aBaseTexturePixels = aBaseTexture.GetPixels();
Color[] aCopyTexturePixels = aToCopyTexture.GetPixels();
int aPixelLength = aBaseTexturePixels.Length;
for(int y1 = y, y2 = 0; y1 < aHeight && y2 < bHeight ; y1++, y2++)
{
for(int x1 = x, x2 = 0 ; x1 < aWidth && x2 < bWidth; x1++, x2++)
{
aBaseTexturePixels[x1 + y1*aWidth] = Color.Lerp(aBaseTexturePixels[x1 + y1*aWidth], aCopyTexturePixels[x2 + y2*bWidth], aCopyTexturePixels[x2 + y2*bWidth].a);
}
}
aReturnTexture.SetPixels(aBaseTexturePixels);
aReturnTexture.Apply(false);
return aReturnTexture;
}
The problem is, that i need to display a lot of sprites on 2d surface (blood, enemy corpses, etc.), and just instantiating every sprite will greatly reduce fps.
If you are concerned about fps drop when instantiating prefabs you should definitely build a Object pooling system. So you will have a system that:
Instantiating all objects in the pool and keep it far away from the main camera
Once you need the object you will "borrow" it from the pool
Once object is not needed anymore you will return it back to the object pool (for example when sprite is out the camera view
Baking it all to one texture isn't the best practice. You will need huge amounts of RAM for this. Consider steps above, its very common practice
Good example here:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Linq;
public class BackgroundPool : MonoBehaviour
{
public static BackgroundPool instance;
public List<BackgroundSection> sectionsLibrary = new List<BackgroundSection>();
public int poolSize = 4;
public List<BackgroundSection> pool = new List<BackgroundSection>();
void Awake()
{
instance = this;
DateTime startGenTime = DateTime.Now;
//generateSectionsPool
for (int i=0; i<sectionsLibrary.Count; i++)
{
for (int j=0; j<poolSize; j++)
{
if (j == 0)
{
sectionsLibrary[i].positionInPool = sectionsLibrary[i].transform.position;
pool.Add(sectionsLibrary[i]);
}
else
{
BackgroundSection section = (BackgroundSection)Instantiate(sectionsLibrary[i]);
section.transform.parent = this.transform;
section.transform.position = new Vector3((-(ExtensionMethods.GetBounds(sectionsLibrary[i].gameObject).extents.x * 2) * j) + sectionsLibrary[i].transform.position.x,
sectionsLibrary[i].transform.position.y);
section.transform.localEulerAngles = Vector3.zero;
section.gameObject.name = sectionsLibrary[i].gameObject.name + ":" + j.ToString();
section.positionInPool = section.transform.position;
pool.Add(section);
}
}
}
Debug.Log("Background Pool generated in: " + (DateTime.Now - startGenTime).TotalSeconds.ToString() + " s");
}
public BackgroundSection GetPiece(Scenery scenery, SceneryLayer _layer)
{
List<BackgroundSection> allScenery = new List<BackgroundSection>();
foreach (BackgroundSection section in pool) { if (section.scenery == scenery) allScenery.Add(section); }
List<BackgroundSection> matchingPieces = new List<BackgroundSection>();
foreach (BackgroundSection section in allScenery) { if (section.sceneryLayer == _layer) matchingPieces.Add(section); }
if (matchingPieces.Count > 0)
{
BackgroundSection pickedSection = matchingPieces[UnityEngine.Random.Range(0,matchingPieces.Count-1)];
pool.Remove(pickedSection);
return pickedSection;
}
else
{
Debug.LogError("Cann't get background piece matching criteria, scenery: " + scenery + ", layer" + _layer);
return null;
}
}
public void ReturnPiece(BackgroundSection section)
{
pool.Add(section);
section.transform.parent = this.transform;
section.transform.position = section.positionInPool;
}
}

Fill polygon in unity 3d

I've some problem when draw manual in unity 2d.
I used list vector to draw polygon, but I can't fill it.
I also read this tutorial: http://forum.unity3d.com/threads/draw-polygon.54092/
But it's seem I need to convert polygon to triangles.(because my polygon is complex so convert to triangles is hard. I need to use some algorithm like Ear clipping...).
Please help me an easy way to fill it. (I think unity is top of game engine, then have some way to do it easiest).
Thanks so so much.
You are stuck with converting to mesh to get fill to work... GPUs(shaders) can only fill the interior spaces of triangles... This is fairly easy if you are working with closed convex polygons. Polygons with concave sections will take a bit more complicated algorithm to convert to mesh, but it seems you've already done some research on the subject (you mentioned ear clipping).
Good luck implementing your polygon list to triangle algo :)
I can offer Poisson-Disc algorithm remodel UniformPoissonDiskSampler.cs like :
using System;
using System.Collections.Generic;
using UnityEngine;
namespace AwesomeNamespace
{
public static class UniformPoissonDiskSampler
{
public const int DefaultPointsPerIteration = 30;
static readonly float SquareRootTwo = (float)Math.Sqrt(2);
struct Settings
{
public UnityEngine.Vector2 TopLeft, LowerRight, Center;
public UnityEngine.Vector2 Dimensions;
public float? RejectionSqDistance;
public float MinimumDistance;
public float CellSize;
public int GridWidth, GridHeight;
}
struct State
{
public UnityEngine.Vector2?[,] Grid;
public List<UnityEngine.Vector2> ActivePoints, Points;
}
public static List<UnityEngine.Vector2> SampleCircle(UnityEngine.Vector2 center, float radius, float minimumDistance)
{
return SampleCircle(center, radius, minimumDistance, DefaultPointsPerIteration);
}
public static List<UnityEngine.Vector2> SampleCircle(UnityEngine.Vector2 center, float radius, float minimumDistance, int pointsPerIteration)
{
return Sample(center - new UnityEngine.Vector2(radius, radius), center + new UnityEngine.Vector2(radius, radius), radius, minimumDistance, pointsPerIteration, null);
}
public static List<UnityEngine.Vector2> SampleRectangle(UnityEngine.Vector2 topLeft, UnityEngine.Vector2 lowerRight, float minimumDistance)
{
return SampleRectangle(topLeft, lowerRight, minimumDistance, DefaultPointsPerIteration);
}
public static List<UnityEngine.Vector2> SampleRectangle(UnityEngine.Vector2 topLeft, UnityEngine.Vector2 lowerRight, float minimumDistance, int pointsPerIteration)
{
return Sample(topLeft, lowerRight, null, minimumDistance, pointsPerIteration, null);
}
public static List<UnityEngine.Vector2> SamplePolygon(UnityEditor.Experimental.TerrainAPI.Processing.InMetric metric, float minimumDistance)
{
return Sample(null, null, null, minimumDistance, DefaultPointsPerIteration, metric);
}
static List<UnityEngine.Vector2> Sample(UnityEngine.Vector2? topLeft, UnityEngine.Vector2? lowerRight, float? rejectionDistance, float minimumDistance, int pointsPerIteration, UnityEditor.Experimental.TerrainAPI.Processing.InMetric metric = null)
{
if (!topLeft.HasValue && !lowerRight.HasValue && metric != null)
{
topLeft = new Vector2(metric.minpointx, metric.minpointz);
lowerRight = new Vector2(metric.maxpointx, metric.maxpointz);
}
var settings = new Settings
{
TopLeft = (Vector2)topLeft,
LowerRight = (Vector2)lowerRight,
Dimensions = (Vector2)lowerRight - (Vector2)topLeft,
Center = ((Vector2)topLeft + (Vector2)lowerRight) / 2,
CellSize = minimumDistance / SquareRootTwo,
MinimumDistance = minimumDistance,
RejectionSqDistance = rejectionDistance == null ? null : rejectionDistance * rejectionDistance
};
settings.GridWidth = (int)(settings.Dimensions.x / settings.CellSize) + 1;
settings.GridHeight = (int)(settings.Dimensions.y / settings.CellSize) + 1;
// Debug.Log("settings.GridWidth"+settings.GridWidth+"settings.GridHeight"+settings.GridHeight);
var state = new State
{
Grid = new UnityEngine.Vector2?[settings.GridWidth, settings.GridHeight],
ActivePoints = new List<UnityEngine.Vector2>(),
Points = new List<UnityEngine.Vector2>()
};
AddFirstPoint(ref settings, ref state, (metric == null) ? null : metric);
while (state.ActivePoints.Count != 0)
{
var listIndex = RandomHelper.Random.Next(state.ActivePoints.Count);
var point = state.ActivePoints[listIndex];
var found = false;
for (var k = 0; k < pointsPerIteration; k++)
found |= AddNextPoint(point, ref settings, ref state, (metric == null) ? null : metric);
if (!found)
state.ActivePoints.RemoveAt(listIndex);
}
return state.Points;
}
static void AddFirstPoint(ref Settings settings,
ref State state,
UnityEditor.Experimental.TerrainAPI.Processing.InMetric metric = null)
{
var added = false;
while (!added)
{
var d = RandomHelper.Random.NextDouble();
var xr = settings.TopLeft.x + settings.Dimensions.x * d;
d = RandomHelper.Random.NextDouble();
var yr = settings.TopLeft.y + settings.Dimensions.y * d;
var p = new UnityEngine.Vector2((float)xr, (float)yr);
if (settings.RejectionSqDistance != null && DistanceSquared(settings.Center, p) > settings.RejectionSqDistance)
continue;
added = true;
if (UnityEditor.Experimental.TerrainAPI.Processing.figures_Included(p.x, p.y, metric.metricIn, metric.count) == true)
{
var index = Denormalize(p, settings.TopLeft, settings.CellSize);
state.Grid[(int)index.x, (int)index.y] = p;
state.ActivePoints.Add(p);
state.Points.Add(p);
}
else
{
AddFirstPoint(ref settings, ref state, metric);
}
}
}
static float DistanceSquared(Vector2 A, Vector2 B)
{
return (float)Math.Pow(Math.Sqrt(Math.Pow((A.x - B.x), 2) + Math.Pow((A.y - B.y), 2)), 2);
}
static bool AddNextPoint(UnityEngine.Vector2 point,
ref Settings settings,
ref State state,
UnityEditor.Experimental.TerrainAPI.Processing.InMetric metric = null)
{
var found = false;
var q = GenerateRandomAround(point, settings.MinimumDistance);
if (metric != null)
{
if (UnityEditor.Experimental.TerrainAPI.Processing.figures_Included(q.x, q.y, metric.metricIn, metric.count) == true &&
q.x >= settings.TopLeft.x && q.x < settings.LowerRight.x &&
q.y > settings.TopLeft.y && q.y < settings.LowerRight.y &&
(settings.RejectionSqDistance == null || DistanceSquared(settings.Center, q) <= settings.RejectionSqDistance))
{
var qIndex = Denormalize(q, settings.TopLeft, settings.CellSize);
var tooClose = false;
for (var i = (int)Math.Max(0, qIndex.x - 2); i < Math.Min(settings.GridWidth, qIndex.x + 3) && !tooClose; i++)
for (var j = (int)Math.Max(0, qIndex.y - 2); j < Math.Min(settings.GridHeight, qIndex.y + 3) && !tooClose; j++)
if (state.Grid[i, j].HasValue && Vector2.Distance(state.Grid[i, j].Value, q) < settings.MinimumDistance)
tooClose = true;
if (!tooClose)
{
found = true;
state.ActivePoints.Add(q);
state.Points.Add(q);
state.Grid[(int)qIndex.x, (int)qIndex.y] = q;
}
}
}
else
{
if (q.x >= settings.TopLeft.x && q.x < settings.LowerRight.x &&
q.y > settings.TopLeft.y && q.y < settings.LowerRight.y &&
(settings.RejectionSqDistance == null || DistanceSquared(settings.Center, q) <= settings.RejectionSqDistance))
{
var qIndex = Denormalize(q, settings.TopLeft, settings.CellSize);
var tooClose = false;
for (var i = (int)Math.Max(0, qIndex.x - 2); i < Math.Min(settings.GridWidth, qIndex.x + 3) && !tooClose; i++)
for (var j = (int)Math.Max(0, qIndex.y - 2); j < Math.Min(settings.GridHeight, qIndex.y + 3) && !tooClose; j++)
if (state.Grid[i, j].HasValue && Vector2.Distance(state.Grid[i, j].Value, q) < settings.MinimumDistance)
tooClose = true;
if (!tooClose)
{
found = true;
state.ActivePoints.Add(q);
state.Points.Add(q);
state.Grid[(int)qIndex.x, (int)qIndex.y] = q;
}
}
}
return found;
}
static Vector2 GenerateRandomAround(Vector2 center, float minimumDistance)
{
var d = RandomHelper.Random.NextDouble();
var radius = minimumDistance + minimumDistance * d;
d = RandomHelper.Random.NextDouble();
var angle = MathHelper.TwoPi * d;
var newX = radius * Math.Sin(angle);
var newY = radius * Math.Cos(angle);
return new Vector2((float)(center.x + newX), (float)(center.y + newY));
}
static Vector2 Denormalize(Vector2 point, Vector2 origin, double cellSize)
{
return new Vector2((int)((point.x - origin.x) / cellSize), (int)((point.y - origin.y) / cellSize));
}
}
public static class RandomHelper
{
public static readonly System.Random Random = new System.Random();
}
public static class MathHelper
{
public const float Pi = (float)Math.PI;
public const float HalfPi = (float)(Math.PI / 2);
public const float TwoPi = (float)(Math.PI * 2);
}
}
figures_Included:
public static bool figures_Included(float xPoint, float yPoint, float[] metricIn, int n)
{
float X = xPoint;
float Y = yPoint;
int npol = n;
int i, j;
bool res = false;
float[] XYpol = metricIn;
for (i = 0, j = npol - 1; i < npol; j = i++)
{
if ((((XYpol[i * 2 + 1] <= Y) && (Y < XYpol[j * 2 + 1])) ||
((XYpol[j * 2 + 1] <= Y) && (Y < XYpol[i * 2 + 1]))) &&
(X < (XYpol[j * 2] - XYpol[i * 2]) * (Y - XYpol[i * 2 + 1]) /
(XYpol[j * 2 + 1] - XYpol[i * 2 + 1]) + XYpol[i * 2]))
{
res = !res;
}
}
return res;
}
and InMetric :
static public InMetric getmetricIn(List<Vector3> drawcoord, bool editingmode = true)
{
float mapoffsetx = 0;
float mapoffsety = 0;
if (editingmode == true)
{
mapoffsetx = Main.mainSatting.mapoffsetx;
mapoffsety = Main.mainSatting.mapoffsetz;
}
else
{
mapoffsetx = 0;
mapoffsety = 0;
}
if (drawcoord[0].x != drawcoord[drawcoord.Count - 1].x && drawcoord[0].z != drawcoord[drawcoord.Count - 1].z) //если линия, ограничивающая полигон не замкнута
drawcoord.Add(drawcoord[0]); //добавляем замыкающую вершину
float[] metricIn = new float[drawcoord.Count * 2]; //дополнительный массив вершин, пересчитанный для проверки нахождения точки внутри полигона
drawcoord[0] = new Vector3(drawcoord[0].x - mapoffsetx, 0, drawcoord[0].z - mapoffsety); //расчет 0-ой вершины в единицах Unity (метры)
metricIn[0] = drawcoord[0].x;
metricIn[1] = drawcoord[0].z; //запись 0-ой вершины в дополнительный массив. x-координаты под четными индексами, Z-координаты под нечетными индексами
float minpointx = drawcoord[0].x; //минимальная x-координата
float maxpointx = drawcoord[0].x; //максимальная х-координата
float minpointz = drawcoord[0].z; //минимальная y-координата
float maxpointz = drawcoord[0].z; //максимальная у-координата
/*Цикл обработки вершин. начинается 1-ой вершины*/
for (int i = 1; i < drawcoord.Count; i++)
{
drawcoord[i] = new Vector3(drawcoord[i].x - mapoffsetx, 0, drawcoord[i].z - mapoffsety); //расчет i-ой вершины в единицах Unity (метры)
metricIn[i * 2] = drawcoord[i].x; //запись i-ой вершины в дополнительный массив. x-координаты под четными индексами
metricIn[i * 2 + 1] = drawcoord[i].z; //запись i-ой вершины в дополнительный массив. z-координаты под нечетными индексами
/*поиск максимальных и минимальных координат по x и максимальных и минимальных координат по z*/
if (drawcoord[i].x < minpointx)
minpointx = drawcoord[i].x;
if (drawcoord[i].x > maxpointx)
maxpointx = drawcoord[i].x;
if (drawcoord[i].z < minpointz)
minpointz = drawcoord[i].z;
if (drawcoord[i].z > maxpointz)
maxpointz = drawcoord[i].z;
}
InMetric metric = new InMetric();
metric.metricIn = metricIn;
metric.minpointx = minpointx;
metric.maxpointx = maxpointx;
metric.minpointz = minpointz;
metric.maxpointz = maxpointz;
metric.drawcoord = drawcoord;
metric.count = drawcoord.Count;
return metric;
}
public class InMetric
{
public float minpointx { get; set; }
public float maxpointx { get; set; }
public float minpointz { get; set; }
public float maxpointz { get; set; }
public float[] metricIn { get; set; }
public List<Vector3> drawcoord { get; set; }
public int count { get; set; }
}