Yo @Pryme8 … I wrote the most awesome Skidmark Manager Class (Again… Using Unity Style Game Mechanics… Actually ported from the UnitySkidmarks.cs)
My Babylon Version:
1…Set up a large EMPTY mesh to hold the skidmarks for all the cars
2… Each car’s wheel call AddSkidmarkSegment when the wheill it actually skidding
3… I update the actual underlying Float32Array directly adding new skid segments
4… A global update runs and applies all the Float32Array buffers Directly to mesh vertex buffer
And we end up with a High Performance skidmarks with virtually no addition overhead… Unless you make REALLY LARGE skidmark mesh… But anything under 65000 verts… About 4096 max Skidmark segments, take no additional MS
Check out my SkidmarkManager.ts
/**
* Babylon skidmark section class (Native Bullet Physics 2.82)
* @class SkidmarkSection
*/
class SkidmarkSection {
public Pos:BABYLON.Vector3 = BABYLON.Vector3.Zero();
public Normal:BABYLON.Vector3 = BABYLON.Vector3.Zero();
public Tangent:BABYLON.Vector4 = BABYLON.Vector4.Zero();
public Posl:BABYLON.Vector3 = BABYLON.Vector3.Zero();
public Posr:BABYLON.Vector3 = BABYLON.Vector3.Zero();
public Intensity:number = 0.0;
public LastIndex:number = -1;
}
/**
* Babylon Script Component
* @class SkidmarkManager
*/
class SkidmarkManager extends BABYLON.ScriptComponent {
private static MAX_MARKS:number = 4096; // Max number of marks total for everyone together
private static GROUND_OFFSET:number = 0.01; // Distance above surface in metres
private static VELOCITY_SCALE:number = 0.02; // Velocity offset scale factor
private static GPU_TRIANGLES:boolean = true; // GPU quad triangle indices only
private static MARK_COLOR:BABYLON.Color3 = BABYLON.Color3.Black(); // Skidmark Color
private static MARK_WIDTH:number = 0.3; // Width of the Skidmarks. Should match the width of the wheels
private static MIN_DISTANCE:number = 0.1; // Distance between skid texture sections in metres. Bigger = better performance, less smooth
private static MIN_SQR_DISTANCE = (SkidmarkManager.MIN_DISTANCE * SkidmarkManager.MIN_DISTANCE);
private static TEXTURE_MARKS:any = null;
private static SkidBufferPositions:Float32Array = null;
private static SkidBufferNormals:Float32Array = null;
private static SkidBufferTangents:Float32Array = null;
private static SkidBufferColors:Float32Array = null;
private static SkidBufferUvs:Float32Array = null;
private static SkidBufferIndices:Int32Array = null;
private static SkidmarkSections:SkidmarkSection[] = null;
private static SkidmarkIndex:number = 0;
private static SkidmarkMesh:BABYLON.Mesh = null;
private static SkidmarkUpdated:boolean = false;
private static TempVector3_POS:BABYLON.Vector3 = new BABYLON.Vector3(0,0,0);
private static TempVector3_DIR:BABYLON.Vector3 = new BABYLON.Vector3(0,0,0);
private static TempVector3_XDIR:BABYLON.Vector3 = new BABYLON.Vector3(0,0,0);
private static TempVector3_SDIR:BABYLON.Vector3 = new BABYLON.Vector3(0,0,0);
public constructor(transform: BABYLON.TransformNode, scene: BABYLON.Scene, properties: any = {}) {
super(transform, scene, properties);
SkidmarkManager.MAX_MARKS = this.getEditorProperty("maxSections", SkidmarkManager.MAX_MARKS);
SkidmarkManager.MARK_COLOR = BABYLON.Utilities.ParseColor3(this.getEditorProperty("textureColor", SkidmarkManager.MARK_COLOR));
SkidmarkManager.MARK_WIDTH = this.getEditorProperty("textureWidth", SkidmarkManager.MARK_WIDTH);
SkidmarkManager.GROUND_OFFSET = this.getEditorProperty("groundOffset", SkidmarkManager.GROUND_OFFSET);
SkidmarkManager.VELOCITY_SCALE = this.getEditorProperty("velocityScale", SkidmarkManager.VELOCITY_SCALE);
SkidmarkManager.GPU_TRIANGLES = this.getEditorProperty("gpuQuadIndices", SkidmarkManager.GPU_TRIANGLES);
SkidmarkManager.TEXTURE_MARKS = this.getEditorProperty("textureMarks", SkidmarkManager.TEXTURE_MARKS);
SkidmarkManager.MIN_DISTANCE = this.getEditorProperty("textureDistance", SkidmarkManager.MIN_DISTANCE);
SkidmarkManager.MIN_SQR_DISTANCE = (SkidmarkManager.MIN_DISTANCE * SkidmarkManager.MIN_DISTANCE);
}
protected start(): void { SkidmarkManager.CreateSkidmarkManager(this.scene); }
protected update(): void { SkidmarkManager.UpdateSkidmarkManager(); }
public static GetVelocityScale():number { return SkidmarkManager.VELOCITY_SCALE; }
public static AddSkidmarkSegment(pos:BABYLON.Vector3, normal:BABYLON.Vector3, intensity:number, lastIndex:number):BABYLON.Nullable<number> {
if (SkidmarkManager.SkidmarkMesh == null) return null;
SkidmarkManager.TempVector3_POS.set(0,0,0);
SkidmarkManager.TempVector3_DIR.set(0,0,0);
SkidmarkManager.TempVector3_XDIR.set(0,0,0);
SkidmarkManager.TempVector3_SDIR.set(0,0,0);
// ..
if (intensity > 1.0) intensity = 1.0;
else if (intensity < 0.0) return -1.0;
// ..
if (lastIndex > 0) {
pos.subtractToRef(SkidmarkManager.SkidmarkSections[lastIndex].Pos, SkidmarkManager.TempVector3_POS);
const sqrDistance:number = SkidmarkManager.TempVector3_POS.lengthSquared();
if (sqrDistance < SkidmarkManager.MIN_SQR_DISTANCE) return lastIndex;
}
// ..
const curSection:SkidmarkSection = SkidmarkManager.SkidmarkSections[SkidmarkManager.SkidmarkIndex];
normal.scaleToRef(SkidmarkManager.GROUND_OFFSET, SkidmarkManager.TempVector3_POS);
pos.addToRef(SkidmarkManager.TempVector3_POS, curSection.Pos);
curSection.Normal.copyFrom(normal);
curSection.Intensity = intensity;
curSection.LastIndex = lastIndex;
// ..
if (lastIndex != -1) {
const lastSection:SkidmarkSection = SkidmarkManager.SkidmarkSections[lastIndex];
curSection.Pos.subtractToRef(lastSection.Pos, SkidmarkManager.TempVector3_DIR);
BABYLON.Vector3.CrossToRef(SkidmarkManager.TempVector3_DIR, normal, SkidmarkManager.TempVector3_XDIR);
SkidmarkManager.TempVector3_XDIR.normalizeToRef(SkidmarkManager.TempVector3_XDIR);
// ..
SkidmarkManager.TempVector3_XDIR.scaleToRef(SkidmarkManager.MARK_WIDTH * 0.5, SkidmarkManager.TempVector3_SDIR)
curSection.Pos.addToRef(SkidmarkManager.TempVector3_SDIR, curSection.Posl);
curSection.Pos.subtractToRef(SkidmarkManager.TempVector3_SDIR, curSection.Posr);
curSection.Tangent.set(SkidmarkManager.TempVector3_XDIR.x, SkidmarkManager.TempVector3_XDIR.y, SkidmarkManager.TempVector3_XDIR.z, 1);
// ..
if (lastSection.LastIndex === -1) {
curSection.Pos.addToRef(SkidmarkManager.TempVector3_SDIR, lastSection.Posl);
curSection.Pos.subtractToRef(SkidmarkManager.TempVector3_SDIR, lastSection.Posr);
lastSection.Tangent.copyFrom(curSection.Tangent);
}
}
// ..
SkidmarkManager.AddSkidmarkVertexData();
const curIndex:number = SkidmarkManager.SkidmarkIndex;
SkidmarkManager.SkidmarkIndex = ++SkidmarkManager.SkidmarkIndex % SkidmarkManager.MAX_MARKS;
return curIndex;
}
private static CreateSkidmarkManager(scene:BABYLON.Scene):void {
if (SkidmarkManager.SkidmarkMesh == null) {
const skidmarkMaterial:BABYLON.StandardMaterial = new BABYLON.StandardMaterial("SkidmarkMaterial", scene);
skidmarkMaterial.backFaceCulling = false;
skidmarkMaterial.disableLighting = true;
skidmarkMaterial.emissiveColor = SkidmarkManager.MARK_COLOR;
skidmarkMaterial.diffuseColor = SkidmarkManager.MARK_COLOR;
skidmarkMaterial.diffuseTexture = BABYLON.Utilities.ParseTexture(SkidmarkManager.TEXTURE_MARKS, scene);
if (skidmarkMaterial.diffuseTexture != null) {
skidmarkMaterial.diffuseTexture.hasAlpha = true;
skidmarkMaterial.useAlphaFromDiffuseTexture = true;
}
skidmarkMaterial.freeze();
SkidmarkManager.SkidmarkMesh = new BABYLON.Mesh("SkidmarkMesh", scene);
SkidmarkManager.SkidmarkMesh.material = skidmarkMaterial;
SkidmarkManager.SkidmarkMesh.alwaysSelectAsActiveMesh = true;
SkidmarkManager.SkidmarkMesh.doNotSyncBoundingInfo = true;
SkidmarkManager.SkidmarkMesh.receiveShadows = false;
SkidmarkManager.SkidmarkMesh.checkCollisions = false;
SkidmarkManager.SkidmarkMesh.useVertexColors = true;
SkidmarkManager.SkidmarkMesh.hasVertexAlpha = true;
SkidmarkManager.SkidmarkMesh.isPickable = false;
// ..
// Setup Skidmark Section Properties
// ..
SkidmarkManager.SkidmarkSections = new Array<SkidmarkSection>(SkidmarkManager.MAX_MARKS);
for (let i = 0; i < SkidmarkManager.SkidmarkSections.length; i++) {
SkidmarkManager.SkidmarkSections[i] = new SkidmarkSection();
}
// ..
// Setup Raw Mesh Vertex Buffer Data
// ..
SkidmarkManager.SkidBufferPositions = new Float32Array(SkidmarkManager.MAX_MARKS * 4 * 3);
SkidmarkManager.SkidBufferNormals = new Float32Array(SkidmarkManager.MAX_MARKS * 4 * 3);
SkidmarkManager.SkidBufferTangents = new Float32Array(SkidmarkManager.MAX_MARKS * 4 * 4);
SkidmarkManager.SkidBufferColors = new Float32Array(SkidmarkManager.MAX_MARKS * 4 * 4);
SkidmarkManager.SkidBufferUvs = new Float32Array(SkidmarkManager.MAX_MARKS * 4 * 2);
SkidmarkManager.SkidBufferIndices = new Int32Array(SkidmarkManager.MAX_MARKS * 6);
// ..
// Apply Raw Vertex Buffer Data To Mesh
// ..
const vertexData = new BABYLON.VertexData();
vertexData.positions = SkidmarkManager.SkidBufferPositions;
vertexData.normals = SkidmarkManager.SkidBufferNormals;
vertexData.tangents = SkidmarkManager.SkidBufferTangents;
vertexData.colors = SkidmarkManager.SkidBufferColors;
vertexData.uvs = SkidmarkManager.SkidBufferUvs;
vertexData.indices = SkidmarkManager.SkidBufferIndices;
vertexData.applyToMesh(SkidmarkManager.SkidmarkMesh, true);
SkidmarkManager.SkidmarkMesh.freezeWorldMatrix();
}
}
private static AddSkidmarkVertexData():void {
const curr:SkidmarkSection = SkidmarkManager.SkidmarkSections[SkidmarkManager.SkidmarkIndex];
if (curr.LastIndex === -1) return;
const last:SkidmarkSection = SkidmarkManager.SkidmarkSections[curr.LastIndex];
SkidmarkManager.SkidmarkUpdated = true;
// ..
// Update Position Buffers Directly
// ..
let index:number = SkidmarkManager.SkidmarkIndex * 4 + 0;
SkidmarkManager.SkidBufferPositions[index * 3] = last.Posl.x;
SkidmarkManager.SkidBufferPositions[(index * 3) + 1] = last.Posl.y;
SkidmarkManager.SkidBufferPositions[(index * 3) + 2] = last.Posl.z;
index = SkidmarkManager.SkidmarkIndex * 4 + 1;
SkidmarkManager.SkidBufferPositions[index * 3] = last.Posr.x;
SkidmarkManager.SkidBufferPositions[(index * 3) + 1] = last.Posr.y;
SkidmarkManager.SkidBufferPositions[(index * 3) + 2] = last.Posr.z;
index = SkidmarkManager.SkidmarkIndex * 4 + 2;
SkidmarkManager.SkidBufferPositions[index * 3] = curr.Posl.x;
SkidmarkManager.SkidBufferPositions[(index * 3) + 1] = curr.Posl.y;
SkidmarkManager.SkidBufferPositions[(index * 3) + 2] = curr.Posl.z;
index = SkidmarkManager.SkidmarkIndex * 4 + 3;
SkidmarkManager.SkidBufferPositions[index * 3] = curr.Posr.x;
SkidmarkManager.SkidBufferPositions[(index * 3) + 1] = curr.Posr.y;
SkidmarkManager.SkidBufferPositions[(index * 3) + 2] = curr.Posr.z;
// ..
// Update Normal Buffers Directly
// ..
index = SkidmarkManager.SkidmarkIndex * 4 + 0;
SkidmarkManager.SkidBufferNormals[index * 3] = last.Normal.x;
SkidmarkManager.SkidBufferNormals[(index * 3) + 1] = last.Normal.y;
SkidmarkManager.SkidBufferNormals[(index * 3) + 2] = last.Normal.z;
index = SkidmarkManager.SkidmarkIndex * 4 + 1;
SkidmarkManager.SkidBufferNormals[index * 3] = last.Normal.x;
SkidmarkManager.SkidBufferNormals[(index * 3) + 1] = last.Normal.y;
SkidmarkManager.SkidBufferNormals[(index * 3) + 2] = last.Normal.z;
index = SkidmarkManager.SkidmarkIndex * 4 + 2;
SkidmarkManager.SkidBufferNormals[index * 3] = curr.Normal.x;
SkidmarkManager.SkidBufferNormals[(index * 3) + 1] = curr.Normal.y;
SkidmarkManager.SkidBufferNormals[(index * 3) + 2] = curr.Normal.z;
index = SkidmarkManager.SkidmarkIndex * 4 + 3;
SkidmarkManager.SkidBufferNormals[index * 3] = curr.Normal.x;
SkidmarkManager.SkidBufferNormals[(index * 3) + 1] = curr.Normal.y;
SkidmarkManager.SkidBufferNormals[(index * 3) + 2] = curr.Normal.z;
// ..
// Update Tangent Buffers Directly
// ..
index = SkidmarkManager.SkidmarkIndex * 4 + 0;
SkidmarkManager.SkidBufferTangents[index * 4] = last.Tangent.x;
SkidmarkManager.SkidBufferTangents[(index * 4) + 1] = last.Tangent.y;
SkidmarkManager.SkidBufferTangents[(index * 4) + 2] = last.Tangent.z;
SkidmarkManager.SkidBufferTangents[(index * 4) + 3] = last.Tangent.w;
index = SkidmarkManager.SkidmarkIndex * 4 + 1;
SkidmarkManager.SkidBufferTangents[index * 4] = last.Tangent.x;
SkidmarkManager.SkidBufferTangents[(index * 4) + 1] = last.Tangent.y;
SkidmarkManager.SkidBufferTangents[(index * 4) + 2] = last.Tangent.z;
SkidmarkManager.SkidBufferTangents[(index * 4) + 3] = last.Tangent.w;
index = SkidmarkManager.SkidmarkIndex * 4 + 2;
SkidmarkManager.SkidBufferTangents[index * 4] = curr.Tangent.x;
SkidmarkManager.SkidBufferTangents[(index * 4) + 1] = curr.Tangent.y;
SkidmarkManager.SkidBufferTangents[(index * 4) + 2] = curr.Tangent.z;
SkidmarkManager.SkidBufferTangents[(index * 4) + 3] = curr.Tangent.w;
index = SkidmarkManager.SkidmarkIndex * 4 + 3;
SkidmarkManager.SkidBufferTangents[index * 4] = curr.Tangent.x;
SkidmarkManager.SkidBufferTangents[(index * 4) + 1] = curr.Tangent.y;
SkidmarkManager.SkidBufferTangents[(index * 4) + 2] = curr.Tangent.z;
SkidmarkManager.SkidBufferTangents[(index * 4) + 3] = curr.Tangent.w;
// ..
// Update Color Buffers Directly
// ..
index = SkidmarkManager.SkidmarkIndex * 4 + 0;
SkidmarkManager.SkidBufferColors[index * 4] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 1] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 2] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 3] = last.Intensity;
index = SkidmarkManager.SkidmarkIndex * 4 + 1;
SkidmarkManager.SkidBufferColors[index * 4] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 1] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 2] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 3] = last.Intensity;
index = SkidmarkManager.SkidmarkIndex * 4 + 2;
SkidmarkManager.SkidBufferColors[index * 4] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 1] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 2] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 3] = curr.Intensity;
index = SkidmarkManager.SkidmarkIndex * 4 + 3;
SkidmarkManager.SkidBufferColors[index * 4] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 1] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 2] = 1.0;
SkidmarkManager.SkidBufferColors[(index * 4) + 3] = curr.Intensity;
// ..
// Update Coord Buffers Directly
// ..
index = SkidmarkManager.SkidmarkIndex * 4 + 0;
SkidmarkManager.SkidBufferUvs[index * 2] = 0;
SkidmarkManager.SkidBufferUvs[(index * 2) + 1] = 0;
index = SkidmarkManager.SkidmarkIndex * 4 + 1;
SkidmarkManager.SkidBufferUvs[index * 2] = 1;
SkidmarkManager.SkidBufferUvs[(index * 2) + 1] = 0;
index = SkidmarkManager.SkidmarkIndex * 4 + 2;
SkidmarkManager.SkidBufferUvs[index * 2] = 0;
SkidmarkManager.SkidBufferUvs[(index * 2) + 1] = 1;
index = SkidmarkManager.SkidmarkIndex * 4 + 3;
SkidmarkManager.SkidBufferUvs[index * 2] = 1;
SkidmarkManager.SkidBufferUvs[(index * 2) + 1] = 1;
// ..
// Update Triangle 1 Buffers Directly (QUAD)
//..
SkidmarkManager.SkidBufferIndices[SkidmarkManager.SkidmarkIndex * 6 + 0] = SkidmarkManager.SkidmarkIndex * 4 + 0;
SkidmarkManager.SkidBufferIndices[SkidmarkManager.SkidmarkIndex * 6 + 2] = SkidmarkManager.SkidmarkIndex * 4 + 1;
SkidmarkManager.SkidBufferIndices[SkidmarkManager.SkidmarkIndex * 6 + 1] = SkidmarkManager.SkidmarkIndex * 4 + 2;
// ..
// Update Triangle 2 Buffers Directly (QUAD)
// ..
SkidmarkManager.SkidBufferIndices[SkidmarkManager.SkidmarkIndex * 6 + 3] = SkidmarkManager.SkidmarkIndex * 4 + 2;
SkidmarkManager.SkidBufferIndices[SkidmarkManager.SkidmarkIndex * 6 + 5] = SkidmarkManager.SkidmarkIndex * 4 + 1;
SkidmarkManager.SkidBufferIndices[SkidmarkManager.SkidmarkIndex * 6 + 4] = SkidmarkManager.SkidmarkIndex * 4 + 3;
}
private static UpdateSkidmarkManager():void {
if (SkidmarkManager.SkidmarkMesh != null && SkidmarkManager.SkidmarkUpdated === true) {
SkidmarkManager.SkidmarkUpdated = false;
if (SkidmarkManager.SkidmarkMesh.geometry != null) {
SkidmarkManager.SkidmarkMesh.geometry.updateVerticesDataDirectly(BABYLON.VertexBuffer.PositionKind, SkidmarkManager.SkidBufferPositions, 0, false);
SkidmarkManager.SkidmarkMesh.geometry.updateVerticesDataDirectly(BABYLON.VertexBuffer.NormalKind, SkidmarkManager.SkidBufferNormals, 0, false);
SkidmarkManager.SkidmarkMesh.geometry.updateVerticesDataDirectly(BABYLON.VertexBuffer.TangentKind, SkidmarkManager.SkidBufferTangents, 0, false);
SkidmarkManager.SkidmarkMesh.geometry.updateVerticesDataDirectly(BABYLON.VertexBuffer.ColorKind, SkidmarkManager.SkidBufferColors, 0, false);
SkidmarkManager.SkidmarkMesh.geometry.updateVerticesDataDirectly(BABYLON.VertexBuffer.UVKind, SkidmarkManager.SkidBufferUvs, 0, false);
SkidmarkManager.SkidmarkMesh.geometry.updateIndices(SkidmarkManager.SkidBufferIndices, 0, SkidmarkManager.GPU_TRIANGLES);
}
}
}
}
And it works beautifully well 
Ill show when we get playground ammo fixed 