static Vector2 ProjectBoundsVertex( WorldToScreenTransformer transformer, Vector3 direction, Vector3 position, Quaternion rotation, Bounds meshBounds, Vector3 scale, int idx, ref float zMin, ref int closestIdx) { var boundsVertex = Vector3.Scale(Vector3.Scale(direction, scale), meshBounds.extents); var vertex = (rotation * boundsVertex); if (vertex.z < zMin) { zMin = vertex.z; closestIdx = idx; } return(transformer.TransformPoint(vertex + position)); }
static MeshDrawInfo PlaceBackgroundObject(NativeArray <MeshInfo> meshInfos, WorldToScreenTransformer transformer, float foregroundObjectSize, Vector3 position, ref Random rand, float scaleMin, float scaleMax, int textureCount) { var meshIndex = rand.NextInt(0, meshInfos.Length); var meshInfo = meshInfos[meshIndex]; // Rotate/resize object var rotation = rand.NextQuaternionRotation(); var scaleRandom = rand.NextFloat(scaleMin, scaleMax); var scale = ObjectPlacementUtilities.ComputeScaleToMatchArea(transformer, position, rotation, meshInfo.Bounds, scaleRandom * foregroundObjectSize); return(new MeshDrawInfo() { MeshIndex = meshIndex, Position = position, Rotation = rotation, Scale = scale * Vector3.one, TextureIndex = rand.NextInt(textureCount) }); }
// Update is called once per frame void Update() { var tfSource = SourceObject.transform; // NOTE: The bounds from the meshfilter mesh are for the un-transformed mesh, // the mesh renderer's mesh already has the transforms (rotation and scale) applied var boundsSource = SourceObject.GetComponent <MeshFilter>().sharedMesh.bounds; var transformer = new WorldToScreenTransformer(m_Camera); m_ProjectedSize = ObjectPlacementUtilities.ComputeProjectedArea( transformer, tfSource.position, tfSource.rotation, tfSource.localScale, boundsSource); m_TextUI.text = $"Area: {m_ProjectedSize:F2} px^2"; foreach (var target in TargetObjects) { var tfTarget = target.transform; var boundsTarget = target.GetComponent <MeshFilter>().sharedMesh.bounds; var scalarTarget = ObjectPlacementUtilities.ComputeScaleToMatchArea( transformer, tfTarget.position, tfTarget.rotation, boundsTarget, m_ProjectedSize); target.transform.localScale = scalarTarget * Vector3.one; } }
public void ComputeProjectedArea_ReturnsCorrectValues(float area, PrimitiveType primitive, Quaternion rotation) { var cameraGo = new GameObject("camera"); var camera = cameraGo.AddComponent <Camera>(); camera.orthographic = true; var cameraViewAreaMeters = camera.orthographicSize * camera.orthographicSize * camera.aspect * 4; var cameraViewAreaPixels = camera.pixelHeight * camera.pixelWidth; var pixelsToMeters = cameraViewAreaMeters / cameraViewAreaPixels; var transformer = new WorldToScreenTransformer(camera); var mesh = GetMeshForPrimitive(primitive); var projectedArea = ObjectPlacementUtilities.ComputeProjectedArea( transformer, Vector3.zero, rotation, mesh.bounds) * pixelsToMeters; Assert.AreApproximatelyEqual(area, projectedArea); var scale = 1.5f; var scaledArea = ObjectPlacementUtilities.ComputeProjectedArea( transformer, Vector3.zero, rotation, mesh.bounds, scale) * pixelsToMeters; Assert.AreApproximatelyEqual(area * scale * scale, scaledArea); GameObject.Destroy(cameraGo); }
protected override JobHandle OnUpdate(JobHandle inputDeps) { if (!initialized) { Initialize(); } if (!initialized) { return(inputDeps); } if (m_CurriculumQuery.CalculateEntityCount() != 1) { return(inputDeps); } using (s_ResetBackgroundObjects.Auto()) { objectCache.ResetAllObjects(); } var entity = m_CurriculumQuery.GetSingletonEntity(); var curriculumState = EntityManager.GetComponentData <CurriculumState>(entity); var statics = EntityManager.GetComponentObject <PlacementStatics>(entity); if (curriculumState.ScaleIndex >= statics.ScaleFactors.Length) { return(inputDeps); } var meshInfos = new NativeArray <MeshInfo>(statics.BackgroundPrefabs.Length, Allocator.TempJob); for (int i = 0; i < statics.BackgroundPrefabs.Length; i++) { // ReSharper disable once UnusedVariable ObjectPlacementUtilities.GetMeshAndMaterial(statics.BackgroundPrefabs[i], out var material, out var meshToDraw); meshInfos[i] = new MeshInfo() { Bounds = meshToDraw.bounds }; } var foregroundObject = statics.ForegroundPrefabs[curriculumState.PrefabIndex]; var foregroundBounds = ObjectPlacementUtilities.ComputeBounds(foregroundObject); var foregroundRotation = ObjectPlacementUtilities.ComposeForegroundRotation(curriculumState, statics.OutOfPlaneRotations, statics.InPlaneRotations); var foregroundScale = ObjectPlacementUtilities.ComputeForegroundScaling( foregroundBounds, statics.ScaleFactors[curriculumState.ScaleIndex]); var transformer = new WorldToScreenTransformer(camera); // NOTE: For perspective projection, size will depend on position within the viewport, but we're approximating // by computing size for the center var foregroundSizePixels = ObjectPlacementUtilities.ComputeProjectedArea( transformer, m_ForegroundCenter, foregroundRotation, foregroundBounds, foregroundScale); var placementRegion = ObjectPlacementUtilities.ComputePlacementRegion(camera, k_PlacementDistance); var areaPlacementRegion = placementRegion.width * placementRegion.height; var cameraSquarePixels = camera.pixelHeight * camera.pixelWidth; var foregroundSizeUnits = foregroundSizePixels * areaPlacementRegion / cameraSquarePixels; // Lazy approximation of how many subdivisions we need to achieve the target density var numCellsSqrt = math.sqrt(m_objectDensity * areaPlacementRegion / foregroundSizeUnits); var numCellsHorizontal = (int)math.round(numCellsSqrt * m_aspectRatio); var numCellsVertical = (int)math.round(numCellsSqrt / m_aspectRatio); var verticalStep = placementRegion.height / numCellsVertical; var horizontalStep = placementRegion.width / numCellsHorizontal; var scale0 = m_Rand.NextFloat(0.9f, 1.5f); var scale1 = m_Rand.NextFloat(0.9f, 1.5f); var scaleMin = math.min(scale0, scale1); var scaleMax = math.max(scale0, scale1); var cameraTf = camera.transform; var placementOrigin = new Vector3(placementRegion.x, placementRegion.y, k_PlacementDistance + cameraTf.position.z); DatasetCapture.ReportMetric(m_ScaleRangeMetric, $@"[ {{ ""scaleMin"": {scaleMin}, ""scaleMax"": {scaleMax} }}]"); var meshesToDraw = new NativeArray <MeshDrawInfo>(numCellsHorizontal * numCellsVertical * numFillPasses, Allocator.TempJob); using (s_PlaceBackgroundObjects.Auto()) { // XXX: Rather than placing a large collection and then looking for gaps, we simply assume that a sufficiently // dense background will not have gaps - rendering way more objects than necessary is still substantially // faster than trying to read the render texture multiple times per frame new PlaceBackgroundObjectsJob() { PlacementOrigin = placementOrigin, Transformer = new WorldToScreenTransformer(camera), NumCellsHorizontal = numCellsHorizontal, NumCellsVertical = numCellsVertical, HorizontalStep = horizontalStep, VerticalStep = verticalStep, ForegroundSize = foregroundSizePixels, MeshInfos = meshInfos, MeshDrawInfos = meshesToDraw, TextureCount = statics.BackgroundImages.Length, Seed = m_Rand.NextUInt(), MinScale = scaleMin, MaxScale = scaleMax }.Schedule(numFillPasses, 1, inputDeps).Complete(); } using (s_DrawMeshes.Auto()) { var properties = new MaterialPropertyBlock(); foreach (var meshDrawInfo in meshesToDraw) { var prefab = statics.BackgroundPrefabs[meshDrawInfo.MeshIndex]; var sceneObject = objectCache.GetOrInstantiate(prefab); ObjectPlacementUtilities.CreateRandomizedHue(properties, backgroundHueMaxOffset, ref m_Rand); // ReSharper disable once Unity.PreferAddressByIdToGraphicsParams properties.SetTexture("_BaseMap", statics.BackgroundImages[meshDrawInfo.TextureIndex]); sceneObject.GetComponentInChildren <MeshRenderer>().SetPropertyBlock(properties); sceneObject.transform.SetPositionAndRotation(meshDrawInfo.Position, meshDrawInfo.Rotation); sceneObject.transform.localScale = meshDrawInfo.Scale; } } // We have finished drawing the meshes in the camera view, but the engine itself will call Render() // at the end of the frame meshInfos.Dispose(); meshesToDraw.Dispose(); var numObjectsExpected = numCellsHorizontal * numCellsVertical * numFillPasses; if (numObjectsExpected != objectCache.NumObjectsActive) { Debug.LogWarning($"BackgroundGenerator should have placed {numObjectsExpected} but is only using " + $"{objectCache.NumObjectsActive} from the cache."); } return(inputDeps); }
unsafe public static float ComputeProjectedArea( WorldToScreenTransformer transformer, Vector3 position, Quaternion rotation, Vector3 scale, Bounds meshBounds) { var zMin = Single.MaxValue; var closestIdx = -1; var projectedVertices = stackalloc Vector2[8]; projectedVertices[0] = ProjectBoundsVertex(transformer, new Vector3(-1, -1, -1), position, rotation, meshBounds, scale, 0, ref zMin, ref closestIdx); projectedVertices[1] = ProjectBoundsVertex(transformer, new Vector3(-1, -1, 1), position, rotation, meshBounds, scale, 1, ref zMin, ref closestIdx); projectedVertices[2] = ProjectBoundsVertex(transformer, new Vector3(-1, 1, -1), position, rotation, meshBounds, scale, 2, ref zMin, ref closestIdx); projectedVertices[3] = ProjectBoundsVertex(transformer, new Vector3(-1, 1, 1), position, rotation, meshBounds, scale, 3, ref zMin, ref closestIdx); projectedVertices[4] = ProjectBoundsVertex(transformer, new Vector3(1, -1, -1), position, rotation, meshBounds, scale, 4, ref zMin, ref closestIdx); projectedVertices[5] = ProjectBoundsVertex(transformer, new Vector3(1, -1, 1), position, rotation, meshBounds, scale, 5, ref zMin, ref closestIdx); projectedVertices[6] = ProjectBoundsVertex(transformer, new Vector3(1, 1, -1), position, rotation, meshBounds, scale, 6, ref zMin, ref closestIdx); projectedVertices[7] = ProjectBoundsVertex(transformer, new Vector3(1, 1, 1), position, rotation, meshBounds, scale, 7, ref zMin, ref closestIdx); Assert.AreNotEqual(-1, closestIdx); var neighborMap = stackalloc int[] { // 0 1, 2, 4, // 1 0, 3, 5, // 2 0, 3, 6, // 3 1, 2, 7, // 4 0, 5, 6, // 5 1, 4, 7, // 6 2, 4, 7, // 7 3, 5, 6 }; // Compute the projected surface area of each bounds tri facing the camera var closestPoint = projectedVertices[closestIdx]; var neighborStartIdx = closestIdx * 3; var totalArea = 0f; var trisAdded = 0; for (var i = 0; i < 2; i++) { var neighborA = projectedVertices[neighborMap[neighborStartIdx + i]]; for (var j = i + 1; j < 3; j++) { var neighborB = projectedVertices[neighborMap[neighborStartIdx + j]]; totalArea += ComputeAreaOfTriangle(closestPoint, neighborA, neighborB) * 2; trisAdded += 2; } } Assert.AreEqual(6, trisAdded); return(totalArea); }
public static float ComputeProjectedArea( WorldToScreenTransformer transformer, Vector3 position, Quaternion rotation, Bounds meshBounds, float scale = 1f) { return(ComputeProjectedArea(transformer, position, rotation, scale * Vector3.one, meshBounds)); }
internal static float ComputeScaleToMatchArea( WorldToScreenTransformer transformer, Vector3 position, Quaternion rotation, Bounds bounds, float projectedAreaTarget) { return(math.sqrt(projectedAreaTarget / ComputeProjectedArea(transformer, position, rotation, bounds))); }