Last active
October 16, 2024 16:06
-
-
Save EternalTamago/fb8784f47ab34d8e1f393d8c2e5bd0ca to your computer and use it in GitHub Desktop.
HeightfieldColliderShapeのサンプル
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // NewGameプロジェクトのSphereとGroundの各Entityをシーンから削除して新たに空のEntityを追加 | |
| // その追加したEntityに以下のスクリプトを追加して実行する | |
| // スペースキーで玉発射 | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Threading.Tasks; | |
| using Xenko.Core; | |
| using Xenko.Core.Mathematics; | |
| using Xenko.Engine; | |
| using Xenko.Input; | |
| using Xenko.Physics; | |
| using Xenko.Physics.Shapes; | |
| using Xenko.Graphics; | |
| using Xenko.Graphics.GeometricPrimitives; | |
| using Xenko.Extensions; | |
| using Xenko.Rendering; | |
| using Xenko.Rendering.Materials; | |
| using Xenko.Rendering.Materials.ComputeColors; | |
| using Buffer = Xenko.Graphics.Buffer; | |
| namespace Heightfield | |
| { | |
| public class BigWaveGround : AsyncScript | |
| { | |
| [DataMember(0)] | |
| public CameraComponent Camera = null; | |
| [DataMember(100)] | |
| public int Width = 64; | |
| [DataMember(200)] | |
| public int Height = 64; | |
| [DataMember(300)] | |
| public Material Material = null; | |
| [DataMember(310)] | |
| public Material BallMaterial = null; | |
| [DataMember(400)] | |
| public float HeightfieldMinMax = 3f; // 高低の最小が-HeightfieldMinMax、最大がHeightfieldMinMax | |
| [DataMember(500)] | |
| public float WaveSpeed = 0.3f; | |
| private List<Material> materialList = new List<Material>(); // BallMaterialがnullの時に使用 | |
| public override async Task Execute() | |
| { | |
| var random = new Random(Environment.TickCount); | |
| // カメラ | |
| if (Camera is null) | |
| { | |
| Camera = SceneSystem.SceneInstance.RootScene.Entities.First(e => e.Components.Any(c => c is CameraComponent)).Get<CameraComponent>(); | |
| } | |
| // 地形のマテリアル | |
| if (Material is null) | |
| { | |
| var texture = CreateCheckeredPatternTexture(2048, 2048, Color.White, Color.Black, 64, 64); | |
| Material = CreateMaterialWithTexture(texture); | |
| } | |
| // ボールのマテリアル | |
| if (BallMaterial is null) | |
| { | |
| materialList.Add(CreateMaterialWithTexture(CreateCheckeredPatternTexture(512, 512, Color.White, Color.Red))); | |
| materialList.Add(CreateMaterialWithTexture(CreateCheckeredPatternTexture(512, 512, Color.White, Color.Yellow))); | |
| materialList.Add(CreateMaterialWithTexture(CreateCheckeredPatternTexture(512, 512, Color.White, Color.Green))); | |
| materialList.Add(CreateMaterialWithTexture(CreateCheckeredPatternTexture(512, 512, Color.White, Color.Cyan))); | |
| materialList.Add(CreateMaterialWithTexture(CreateCheckeredPatternTexture(512, 512, Color.White, Color.Blue))); | |
| materialList.Add(CreateMaterialWithTexture(CreateCheckeredPatternTexture(512, 512, Color.White, Color.Purple))); | |
| } | |
| // 地形モデルの頂点リスト | |
| VertexPositionNormalTexture[] vertices = null; | |
| // 地形モデルの頂点バッファ(GPU上?) | |
| Buffer<VertexPositionNormalTexture> buffer = null; | |
| // Heightfieldのポイントリスト | |
| UnmanagedArray<float> points = null; | |
| // 地形となるEntityを作成 | |
| var entity = new Entity(); | |
| // 地形のモデルを設定 | |
| var modelComponent = entity.GetOrCreate<ModelComponent>(); | |
| modelComponent.Model = new Model | |
| { | |
| CreatePlane(Width, Height, out vertices, out buffer), | |
| Material, | |
| }; | |
| // HeightfieldColliderShapeを設定 | |
| var staticCollider = entity.GetOrCreate<StaticColliderComponent>(); | |
| staticCollider.ColliderShapes.Add(new BoxColliderShapeDesc()); // ダミー(?) | |
| HeightfieldColliderShape heightfield = CreateHeightfield(Width, Height, -HeightfieldMinMax, HeightfieldMinMax, vertices.Select(v => v.Position.Y).ToArray(), out points); | |
| staticCollider.ColliderShape = heightfield; | |
| // 地形となるEntityをシーンに追加 | |
| SceneSystem.SceneInstance.RootScene.Entities.Add(entity); | |
| while (Game.IsRunning) | |
| { | |
| await Script.NextFrame(); | |
| // スペースキーでボールを射出 | |
| if (Input.IsKeyPressed(Keys.Space)) | |
| { | |
| if (BallMaterial is null) | |
| { | |
| EmitBall(Camera, materialList[random.Next(0, materialList.Count)]); | |
| } | |
| else | |
| { | |
| EmitBall(Camera, BallMaterial); | |
| } | |
| } | |
| // 地形モデルの頂点とHeightfieldColliderShapeのポイントを更新 | |
| Update(Width, Height, vertices, buffer, points); | |
| } | |
| } | |
| private Texture CreateCheckeredPatternTexture(int width, int height, Color first, Color second, int squareWidth = 0, int squareHeight = 0) | |
| { | |
| squareWidth = squareWidth <= 0 ? width / 2 : squareWidth; | |
| squareHeight = squareHeight <= 0 ? height / 2 : squareHeight; | |
| Color[] pixels = new Color[width * height]; | |
| void SetPixel(int x, int y, Color value) => pixels[y * width + x] = value; | |
| for (int y = 0; y < height; ++y) | |
| { | |
| for (int x = 0; x < width; ++x) | |
| { | |
| if ((((x / squareWidth) & 1) + (y / squareHeight & 1)) == 1) | |
| { | |
| SetPixel(x, y, second); | |
| } | |
| else | |
| { | |
| SetPixel(x, y, first); | |
| } | |
| } | |
| } | |
| return Texture.New2D(GraphicsDevice, width, height, PixelFormat.R8G8B8A8_UNorm, pixels); | |
| } | |
| private Material CreateMaterialWithTexture(Texture texture) | |
| { | |
| var descriptor = new MaterialDescriptor | |
| { | |
| Attributes = new MaterialAttributes | |
| { | |
| MicroSurface = new MaterialGlossinessMapFeature(new ComputeFloat(0.1f)) | |
| { | |
| Invert = false, | |
| }, | |
| Diffuse = new MaterialDiffuseMapFeature | |
| { | |
| DiffuseMap = new ComputeTextureColor | |
| { | |
| Texture = texture, | |
| FallbackValue = new ComputeColor(Color4.White), | |
| TexcoordIndex = TextureCoordinate.Texcoord0, | |
| AddressModeU = TextureAddressMode.Wrap, | |
| AddressModeV = TextureAddressMode.Wrap, | |
| Filtering = TextureFilter.Linear, | |
| Offset = Vector2.Zero, | |
| Scale = Vector2.One, | |
| } | |
| }, | |
| DiffuseModel = new MaterialDiffuseLambertModelFeature(), | |
| CullMode = CullMode.Back, | |
| Specular = new MaterialMetalnessMapFeature(new ComputeFloat(0f)), | |
| SpecularModel = new MaterialSpecularMicrofacetModelFeature | |
| { | |
| Fresnel = new MaterialSpecularMicrofacetFresnelSchlick(), | |
| Visibility = new MaterialSpecularMicrofacetVisibilitySmithSchlickGGX(), | |
| NormalDistribution = new MaterialSpecularMicrofacetNormalDistributionGGX(), | |
| }, | |
| }, | |
| }; | |
| return Material.New(GraphicsDevice, descriptor); | |
| } | |
| private Mesh CreatePlane(int width, int height, out VertexPositionNormalTexture[] resultVertices, out Buffer<VertexPositionNormalTexture> resultBuffer) | |
| { | |
| // 頂点リスト | |
| var vertices = new VertexPositionNormalTexture[(width + 1) * (height + 1)]; | |
| int GetIndex(int x, int y) => y * (width + 1) + x; | |
| var offset = new Vector3(-width / 2, 0f, -height / 2); | |
| float stepU = 1f / width; | |
| float stepV = 1f / height; | |
| for (int y = 0; y <= height; ++y) | |
| { | |
| for (int x = 0; x <= width; ++x) | |
| { | |
| vertices[GetIndex(x, y)] = new VertexPositionNormalTexture | |
| { | |
| Position = new Vector3(x, 0, y) + offset, | |
| Normal = Vector3.UnitY, | |
| TextureCoordinate = new Vector2(stepU * x, stepV * y), | |
| }; | |
| } | |
| } | |
| // インデックスデータ | |
| var indices = new int[width * height * 6]; | |
| var count = 0; | |
| for (int y = 0; y < height; ++y) | |
| { | |
| for (int x = 0; x < width; ++x) | |
| { | |
| indices[count++] = GetIndex(x, y); | |
| indices[count++] = GetIndex(x + 1, y); | |
| indices[count++] = GetIndex(x, y + 1); | |
| indices[count++] = GetIndex(x + 1, y); | |
| indices[count++] = GetIndex(x + 1, y + 1); | |
| indices[count++] = GetIndex(x, y + 1); | |
| } | |
| } | |
| // 頂点のレイアウト | |
| var layout = new VertexDeclaration( | |
| VertexElement.Position<Vector3>(), | |
| VertexElement.Normal<Vector3>(), | |
| VertexElement.TextureCoordinate<Vector2>()); | |
| // 頂点バッファ | |
| var buffer = Buffer.Vertex.New(GraphicsDevice, vertices, GraphicsResourceUsage.Dynamic); | |
| var vertexBufferBinding = new VertexBufferBinding(buffer, layout, vertices.Length); | |
| // インデックスバッファ | |
| var indexBuffer = Buffer.Index.New(GraphicsDevice, indices, GraphicsResourceUsage.Default); | |
| var indexBufferBinding = new IndexBufferBinding(indexBuffer, true, indices.Length); | |
| // MeshDrawの作成 | |
| var meshDraw = new MeshDraw | |
| { | |
| StartLocation = 0, | |
| PrimitiveType = PrimitiveType.TriangleList, | |
| VertexBuffers = new[] { vertexBufferBinding }, | |
| IndexBuffer = indexBufferBinding, | |
| DrawCount = indexBuffer.ElementCount | |
| }; | |
| // Meshの作成 | |
| var boundingBox = BoundingBox.FromPoints(vertices.Select(v => v.Position).ToArray()); | |
| var mesh = new Mesh | |
| { | |
| Draw = meshDraw, | |
| BoundingBox = boundingBox, | |
| BoundingSphere = BoundingSphere.FromBox(boundingBox), | |
| }; | |
| resultVertices = vertices; | |
| resultBuffer = buffer; | |
| return mesh; | |
| } | |
| private HeightfieldColliderShape CreateHeightfield(int width, int length, float min, float max, float[] defaultValues, out UnmanagedArray<float> points) | |
| { | |
| // 大きさを実際のheight stickの数に変換 | |
| width++; | |
| length++; | |
| points = new UnmanagedArray<float>(width * length); | |
| points.Write(defaultValues); | |
| return new HeightfieldColliderShapeWithoutDebugPrimitiveCrash(width, length, points, 1f, min, max, false); | |
| } | |
| private void Update(int width, int height, VertexPositionNormalTexture[] vertices, Buffer<VertexPositionNormalTexture> buffer, UnmanagedArray<float> points) | |
| { | |
| var commandList = Game.GraphicsContext.CommandList; | |
| int GetIndex(int x, int y) => y * (width + 1) + x; | |
| for (int y = 0; y <= height; ++y) | |
| { | |
| for (int x = 0; x <= width; ++x) | |
| { | |
| var newHeight = (float)Math.Cos(Game.UpdateTime.Total.TotalSeconds * WaveSpeed + (MathUtil.TwoPi * x / width - MathUtil.Pi)) * HeightfieldMinMax; | |
| var index = GetIndex(x, y); | |
| vertices[index].Position.Y = newHeight; | |
| points[index] = newHeight; | |
| } | |
| } | |
| buffer.SetData(commandList, vertices); | |
| } | |
| public class LifeTimeOfBall : SyncScript | |
| { | |
| public override void Update() | |
| { | |
| if (Entity.Transform.Position.Y < -50f) | |
| { | |
| Entity.Scene.Entities.Remove(Entity); | |
| Entity.Dispose(); | |
| } | |
| } | |
| } | |
| private void EmitBall(CameraComponent camera, Material material) | |
| { | |
| var direction = Vector3.Transform(-Vector3.UnitZ, camera.Entity.Transform.Rotation); | |
| var entity = new Entity(camera.Entity.Transform.Position); | |
| entity.GetOrCreate<ModelComponent>().Model = new Model | |
| { | |
| new Mesh { Draw = GeometricPrimitive.Sphere.New(GraphicsDevice).ToMeshDraw() }, | |
| material, | |
| }; | |
| var rigidbody = entity.GetOrCreate<RigidbodyComponent>(); | |
| rigidbody.ColliderShapes.Add(new SphereColliderShapeDesc { Is2D = false, LocalOffset = Vector3.Zero, Radius = 0.5f }); | |
| entity.Add(new LifeTimeOfBall()); | |
| Entity.Scene.Entities.Add(entity); | |
| rigidbody.ApplyImpulse(direction * 10f); | |
| } | |
| public class HeightfieldColliderShapeWithoutDebugPrimitiveCrash : HeightfieldColliderShape | |
| { | |
| public HeightfieldColliderShapeWithoutDebugPrimitiveCrash(int heightStickWidth, int heightStickLength, UnmanagedArray<float> dynamicFieldData, float heightScale, float minHeight, float maxHeight, bool flipQuadEdges) | |
| : base(heightStickWidth, heightStickLength, dynamicFieldData, heightScale, minHeight, maxHeight, flipQuadEdges) | |
| { | |
| // DebugPhysicsShapesでデバッグプリミティブを表示しようとしたときにクラッシュするのを回避する | |
| // ColliderShapeTypesに無い値を指定する(現状0-7がある) | |
| Type = (ColliderShapeTypes)Enum.ToObject(typeof(ColliderShapeTypes), 10); | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment