Skip to content

Instantly share code, notes, and snippets.

@EternalTamago
Last active October 16, 2024 16:06
Show Gist options
  • Select an option

  • Save EternalTamago/fb8784f47ab34d8e1f393d8c2e5bd0ca to your computer and use it in GitHub Desktop.

Select an option

Save EternalTamago/fb8784f47ab34d8e1f393d8c2e5bd0ca to your computer and use it in GitHub Desktop.
HeightfieldColliderShapeのサンプル
// 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