package iron.object; import iron.Scene; import iron.data.SceneFormat; import iron.math.Vec4; import iron.math.Quat; import iron.math.Mat4; import iron.data.Data; import iron.data.MeshData; import iron.data.MaterialData; import haxe.ds.Vector; import kha.arrays.Float32Array; import kha.arrays.Uint32Array; import kha.arrays.Int16Array; import kha.Color; import leenkx.trait.internal.RenderDraw; class CurveObject extends Object { public var data: TCurveData; public var splinesLength: Int; public var equidistantSamples: Int = 0; public var curveMesh: MeshObject = null; static var _p0 = new Vec4(); static var _p1 = new Vec4(); static var _p2 = new Vec4(); static var _p3 = new Vec4(); static var _v1 = new Vec4(); static var _v2 = new Vec4(); public function new(data: TCurveData) { super(); this.data = copyCurveData(data); splinesLength = this.data.splines.length; if (this.data.shape_keys != null && this.data.shape_keys.length > 0) applyShapeKeys(); if (this.data.material_refs != null && this.data.material_refs.length > 0) addMeshObject(); else draw(this.data.strength, Color.fromFloats(this.data.color[0], this.data.color[1], this.data.color[2], this.data.color[3])); } public function addMeshObject(){ Data.getMesh("mesh_" + data.name, data.name, function(meshData: MeshData) { var materials = new Vector(data.material_refs.length); var materialsLoaded = 0; for (i in 0...data.material_refs.length) { var ref = data.material_refs[i]; Data.getMaterial(Scene.active.raw.name, ref, function(mat: MaterialData) { materials[i] = mat; materialsLoaded++; if (materialsLoaded == data.material_refs.length) { curveMesh = new MeshObject(meshData, materials); curveMesh.name = this.data.object + "_mesh"; curveMesh.raw = cast { name: this.data.object + "_mesh", type: "mesh_object", }; curveMesh.setParent(this); curveMesh.addTrait(new leenkx.trait.internal.UniformsManager()); } }); } }); } public static function copyCurveData(data: TCurveData): TCurveData { var newData: TCurveData = { name: data.name, object: data.object, splines: [], strength: data.strength, color: copyFloat32Array(data.color), material_refs: data.material_refs, shape_keys: data.shape_keys }; for (spline in data.splines) { var newSpline = { points: [], closed: spline.closed, resolution: spline.resolution, material_index: spline.material_index }; for (p in spline.points) { newSpline.points.push({ co: copyFloat32Array(p.co), handle_left: copyFloat32Array(p.handle_left), handle_right: copyFloat32Array(p.handle_right) }); } newData.splines.push(newSpline); } return newData; } public static function copyFloat32Array(arr: Float32Array): Float32Array { var newArr = new Float32Array(arr.length); for (i in 0...arr.length) newArr[i] = arr[i]; return newArr; } public function getPoint(t: Float, splineIndex: Int = 0): Vec4 { if (data.splines == null || splinesLength <= splineIndex) return new Vec4(); if (equidistantSamples <= 0) { return getBezierPoint(t, splineIndex); } else { return getPointEquidistant(t, splineIndex, equidistantSamples); } } public function getBezierPoint(t: Float, splineIndex: Int = 0): Vec4 { if (data.splines == null || splinesLength <= splineIndex) return new Vec4(); var spline = data.splines[splineIndex]; var points = spline.points; if (points.length < 2) return new Vec4(points[0].co[0], points[0].co[1], points[0].co[2]); t = Math.max(0.0, Math.min(1.0, t)); if (t >= 1.0) { var p = spline.closed ? points[0] : points[points.length - 1]; return new Vec4(p.co[0], p.co[1], p.co[2]); } var numSegments = spline.closed ? points.length : points.length - 1; var progress = t * numSegments; var index = Std.int(progress); var localT = progress - index; var pK = points[index]; var pK1 = (spline.closed && index == points.length - 1) ? points[0] : points[index + 1]; _p0.set(pK.co[0], pK.co[1], pK.co[2]); _p1.set(pK.handle_right[0], pK.handle_right[1], pK.handle_right[2]); _p2.set(pK1.handle_left[0], pK1.handle_left[1], pK1.handle_left[2]); _p3.set(pK1.co[0], pK1.co[1], pK1.co[2]); if (spline.resolution == 12) { return interpBezier(localT, _p0, _p1, _p2, _p3); } else { var stepProgress = localT * spline.resolution; var stepIndex = Std.int(stepProgress); var stepT = stepProgress - stepIndex; var tA = stepIndex / spline.resolution; var tB = (stepIndex + 1) / spline.resolution; var pA = interpBezier(tA, _p0, _p1, _p2, _p3); var pB = interpBezier(tB, _p0, _p1, _p2, _p3); return new Vec4( pA.x + (pB.x - pA.x) * stepT, pA.y + (pB.y - pA.y) * stepT, pA.z + (pB.z - pA.z) * stepT ); } } public function getPointEquidistant(t: Float, splineIndex: Int = 0, samples: Int = 100): Vec4 { if (data.splines == null || splinesLength <= splineIndex) return new Vec4(); var spline = data.splines[splineIndex]; var totalSteps = samples; if (spline.resolution != 12) { var numSegments = spline.closed ? spline.points.length : spline.points.length - 1; totalSteps = spline.resolution * numSegments; if (totalSteps < 1) totalSteps = 1; } var table = [0.0]; var totalLength = 0.0; var lastP = getBezierPoint(0, splineIndex); for (i in 1...totalSteps + 1) { var p = getBezierPoint(i / totalSteps, splineIndex); totalLength += lastP.distanceTo(p); table.push(totalLength); lastP = p; } var targetDistance = Math.max(0.0, Math.min(1.0, t)) * totalLength; var low = 0; var high = table.length - 1; while (low < high - 1) { var mid = Std.int((low + high) / 2); if (table[mid] < targetDistance) low = mid; else high = mid; } var distStart = table[low]; var distEnd = table[high]; var segmentLength = distEnd - distStart; var localT = (segmentLength <= 0) ? 0 : (targetDistance - distStart) / segmentLength; var correctedT = (low + localT) / (table.length - 1); return getBezierPoint(correctedT, splineIndex); } function interpBezier(t: Float, p0: Vec4, p1: Vec4, p2: Vec4, p3: Vec4): Vec4 { var it = 1.0 - t; var b0 = it * it * it; var b1 = 3.0 * t * it * it; var b2 = 3.0 * t * t * it; var b3 = t * t * t; return new Vec4( p0.x * b0 + p1.x * b1 + p2.x * b2 + p3.x * b3, p0.y * b0 + p1.y * b1 + p2.y * b2 + p3.y * b3, p0.z * b0 + p1.z * b1 + p2.z * b2 + p3.z * b3 ); } public function getTangent(t: Float, splineIndex: Int = 0): Vec4 { var delta = 0.001; var t1 = Math.max(0.0, t - delta); var t2 = Math.min(1.0, t + delta); var p1 = getPoint(t1, splineIndex); var p2 = getPoint(t2, splineIndex); var tangent = new Vec4(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z); tangent.normalize(); return tangent; } public function getClosestPoint(target: Vec4, splineIndex: Int = 0): Float { var minD = 1e10; var bestT = 0.0; var spline = data.splines[splineIndex]; var segments = spline.closed ? spline.points.length : spline.points.length - 1; var steps = equidistantSamples > 0 ? equidistantSamples : Std.int(spline.resolution * segments); if (steps < 1) steps = 1; for (i in 0...steps + 1) { var t = i / steps; var p = getPoint(t, splineIndex); var d = p.distanceTo(target); if (d < minD) { minD = d; bestT = t; } } return bestT; } public function getLength(splineIndex: Int = 0): Float { var length = 0.0; var lastP = getPoint(0, splineIndex); var spline = data.splines[splineIndex]; var segments = spline.closed ? spline.points.length : spline.points.length - 1; var steps = equidistantSamples > 0 ? equidistantSamples : Std.int(spline.resolution * segments); if (steps < 1) steps = 1; for (i in 1...steps + 1) { var p = getPoint(i / steps, splineIndex); length += lastP.distanceTo(p); lastP = p; } return length; } var renderCb: kha.graphics4.Graphics->Int->Int->Void = null; public function draw(strength: Float = 0.005, color: kha.Color = Color.Black) { if (renderCb != null) RenderDraw.removeOnRender(renderCb); renderCb = RenderDraw.notifyOnRender(function(draw: RenderDraw) { if (!visible) return; draw.color = color; draw.strength = strength; var worldMat = this.transform.world; for (s in 0...splinesLength) { var spline = data.splines[s]; var segments = spline.closed ? spline.points.length : spline.points.length - 1; var totalSteps = spline.resolution * segments; var step = 1.0 / (totalSteps); for (i in 0...totalSteps) { var t1 = i * step; var t2 = (i + 1) * step; var p1 = getPoint(t1, s); var p2 = getPoint(t2, s); p1.applymat4(worldMat); p2.applymat4(worldMat); draw.linev(p1, p2); } } }); } var _q = new Quat(); var _m = Mat4.identity(); var _f = new Vec4(); var _r = new Vec4(); var _u = new Vec4(); var _z = new Vec4(); var _vx = new Vec4(); var _vy = new Vec4(); var _vz = new Vec4(); public function follow(obj: Object, t: Float, splineIndex: Int = 0, forwardAxis: String = "X") { var pos = getPoint(t, splineIndex); pos.applymat4(this.transform.world); obj.transform.loc.setFrom(pos); _f.setFrom(getTangent(t, splineIndex)); _f.applyQuat(this.transform.rot); _f.normalize(); _z.set(0, 0, 1); if (Math.abs(_f.dot(_z)) > 0.9999) { _z.set(0, 1, 0); } _r.crossvecs(_f, _z).normalize(); _u.crossvecs(_r, _f).normalize(); switch (forwardAxis) { case "X": _vx.setFrom(_f); _vy.set(-_r.x, -_r.y, -_r.z); _vz.setFrom(_u); case "-X": _vx.set(-_f.x, -_f.y, -_f.z); _vy.setFrom(_r); _vz.setFrom(_u); case "Y": _vx.setFrom(_r); _vy.setFrom(_f); _vz.setFrom(_u); case "-Y": _vx.set(-_r.x, -_r.y, -_r.z); _vy.set(-_f.x, -_f.y, -_f.z); _vz.setFrom(_u); case "Z": _vx.setFrom(_r); _vy.set(-_u.x, -_u.y, -_u.z); _vz.setFrom(_f); case "-Z": _vx.setFrom(_r); _vy.setFrom(_u); _vz.set(-_f.x, -_f.y, -_f.z); } _m._00 = _vx.x; _m._01 = _vx.y; _m._02 = _vx.z; _m._10 = _vy.x; _m._11 = _vy.y; _m._12 = _vy.z; _m._20 = _vz.x; _m._21 = _vz.y; _m._22 = _vz.z; _q.fromRotationMat(_m); obj.transform.rot.setFrom(_q); obj.transform.buildMatrix(); } override public function remove() { visible = false; if (renderCb != null) { RenderDraw.removeOnRender(renderCb); renderCb = null; } super.remove(); } public function setShapeKey(name: String, value: Float) { if (data.shape_keys == null) return; for (key in data.shape_keys) { if (key.name == name) { key.value = value; break; } } applyShapeKeys(); } public function applyShapeKeys() { var basis = data.shape_keys[0]; for (s in 0...data.splines.length) { var spline = data.splines[s]; for (i in 0...spline.points.length) { var pt = spline.points[i]; var basisPt = basis.points[i]; pt.co[0] = basisPt.co[0]; pt.co[1] = basisPt.co[1]; pt.co[2] = basisPt.co[2]; pt.handle_left[0] = basisPt.handle_left[0]; pt.handle_left[1] = basisPt.handle_left[1]; pt.handle_left[2] = basisPt.handle_left[2]; pt.handle_right[0] = basisPt.handle_right[0]; pt.handle_right[1] = basisPt.handle_right[1]; pt.handle_right[2] = basisPt.handle_right[2]; for (k in 1...data.shape_keys.length) { var key = data.shape_keys[k]; var val = key.value; if (val == 0) continue; var keyPt = key.points[i]; pt.co[0] += (keyPt.co[0] - basisPt.co[0]) * val; pt.co[1] += (keyPt.co[1] - basisPt.co[1]) * val; pt.co[2] += (keyPt.co[2] - basisPt.co[2]) * val; pt.handle_left[0] += (keyPt.handle_left[0] - basisPt.handle_left[0]) * val; pt.handle_left[1] += (keyPt.handle_left[1] - basisPt.handle_left[1]) * val; pt.handle_left[2] += (keyPt.handle_left[2] - basisPt.handle_left[2]) * val; pt.handle_right[0] += (keyPt.handle_right[0] - basisPt.handle_right[0]) * val; pt.handle_right[1] += (keyPt.handle_right[1] - basisPt.handle_right[1]) * val; pt.handle_right[2] += (keyPt.handle_right[2] - basisPt.handle_right[2]) * val; } } } } public function generateBevelMesh(bevelDepth: Float = 0.05, bevelResolution: Int = 8, factorStart: Float = 0.0, factorEnd: Float = 1.0, fillCaps: Bool = false): MeshData { if (data.splines == null || splinesLength == 0) return null; var numSides = bevelResolution <= 0 ? 4 : (bevelResolution * 2 + 4); var rawPositions: Array = []; var rawNormals: Array = []; var rawUVs: Array = []; var indicesByMaterial = new Map>(); for (splineIndex in 0...splinesLength) { var spline = data.splines[splineIndex]; var matIdx = spline.material_index != null ? spline.material_index : 0; if (!indicesByMaterial.exists(matIdx)) indicesByMaterial.set(matIdx, []); var indices = indicesByMaterial.get(matIdx); var segments = spline.closed ? spline.points.length : spline.points.length - 1; var totalSteps = equidistantSamples > 0 ? equidistantSamples : Std.int(spline.resolution * segments); if (totalSteps < 1) totalSteps = 1; var startStep = Std.int(totalSteps * Math.max(0.0, Math.min(1.0, factorStart))); var endStep = Std.int(totalSteps * Math.max(0.0, Math.min(1.0, factorEnd))); var isClosedLoop = spline.closed && factorStart == 0.0 && factorEnd == 1.0; var numRings = endStep - startStep + 1; if (numRings < 2) continue; var baseIndex = rawPositions.length; var tangent = getTangent(factorStart, splineIndex); if (spline.closed && (factorStart == 0.0 || factorStart == 1.0)) { var tIn = getTangent(1.0, splineIndex); var tOut = getTangent(0.0, splineIndex); tangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); tangent.normalize(); } var normal = new Vec4(0, 1, 0); if (Math.abs(tangent.dot(normal)) > 0.9) normal.set(1, 0, 0); var binormal = new Vec4(); binormal.crossvecs(tangent, normal); binormal.normalize(); normal.crossvecs(binormal, tangent); normal.normalize(); var lastTangent = tangent.clone(); for (s in 0...numRings) { var stepIdx = startStep + s; var t = stepIdx / totalSteps; var p = getPoint(t, splineIndex); var currTangent = getTangent(t, splineIndex); if (spline.closed && (t == 0.0 || t == 1.0)) { var tIn = getTangent(1.0, splineIndex); var tOut = getTangent(0.0, splineIndex); currTangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); currTangent.normalize(); } var axis = new Vec4(); axis.crossvecs(lastTangent, currTangent); var dot = Math.max(-1.0, Math.min(1.0, lastTangent.dot(currTangent))); if (axis.length() > 0.00001) { axis.normalize(); var angle = Math.acos(dot); var q = new Quat(); q.fromAxisAngle(axis, angle); normal.applyQuat(q); binormal.applyQuat(q); } lastTangent = currTangent; for (r in 0...(numSides + 1)) { var idx = r % numSides; var angle = (idx / numSides) * Math.PI * 2.0; var cosA = Math.cos(angle); var sinA = Math.sin(angle); var dir = new Vec4( normal.x * cosA + binormal.x * sinA, normal.y * cosA + binormal.y * sinA, normal.z * cosA + binormal.z * sinA ); dir.normalize(); var vPos = new Vec4( p.x + dir.x * bevelDepth, p.y + dir.y * bevelDepth, p.z + dir.z * bevelDepth ); if (isClosedLoop && s == numRings - 1) { vPos = rawPositions[baseIndex + r].clone(); dir = rawNormals[baseIndex + r].clone(); } rawPositions.push(vPos); rawNormals.push(dir); rawUVs.push(new Vec4(t, r / numSides, 0)); } } var stride = numSides + 1; for (s in 0...numRings) { var nextS = s + 1; if (s == numRings - 1) continue; for (r in 0...numSides) { var v0 = baseIndex + s * stride + r; var v1 = baseIndex + s * stride + r + 1; var v2 = baseIndex + nextS * stride + r; var v3 = baseIndex + nextS * stride + r + 1; indices.push(v0); indices.push(v2); indices.push(v1); indices.push(v1); indices.push(v2); indices.push(v3); } } var isPartial = factorStart > 0.0 || factorEnd < 1.0; if (fillCaps && (!spline.closed || isPartial)) { var tStart = factorStart; var startTangent = getTangent(tStart, splineIndex); var capStartNormal = new Vec4(-startTangent.x, -startTangent.y, -startTangent.z); var pStart = new Vec4(0, 0, 0); for (r in 0...numSides) { var pos = rawPositions[baseIndex + r]; pStart.add(pos); } pStart.mult(1.0 / numSides); var capStartCenterIdx = rawPositions.length; rawPositions.push(pStart); rawNormals.push(capStartNormal); rawUVs.push(new Vec4(0.5, 0.5, 0)); var capStartRingBase = rawPositions.length; for (r in 0...numSides) { var origIdx = baseIndex + r; var angle = (r / numSides) * Math.PI * 2.0; var u = 0.5 + 0.5 * Math.cos(angle); var v = 0.5 + 0.5 * Math.sin(angle); rawPositions.push(rawPositions[origIdx].clone()); rawNormals.push(capStartNormal); rawUVs.push(new Vec4(u, v, 0)); } for (r in 0...numSides) { var nextR = (r + 1) % numSides; var v0 = capStartRingBase + r; var v1 = capStartRingBase + nextR; indices.push(capStartCenterIdx); indices.push(v1); indices.push(v0); } var tEnd = factorEnd; var endTangent = getTangent(tEnd, splineIndex); var capEndNormal = endTangent.clone(); var lastRingBase = baseIndex + (numRings - 1) * stride; var pEnd = new Vec4(0, 0, 0); for (r in 0...numSides) { var pos = rawPositions[lastRingBase + r]; pEnd.add(pos); } pEnd.mult(1.0 / numSides); var capEndCenterIdx = rawPositions.length; rawPositions.push(pEnd); rawNormals.push(capEndNormal); rawUVs.push(new Vec4(0.5, 0.5, 0)); var capEndRingBase = rawPositions.length; for (r in 0...numSides) { var origIdx = lastRingBase + r; var angle = (r / numSides) * Math.PI * 2.0; var u = 0.5 + 0.5 * Math.cos(angle); var v = 0.5 + 0.5 * Math.sin(angle); rawPositions.push(rawPositions[origIdx].clone()); rawNormals.push(capEndNormal); rawUVs.push(new Vec4(u, v, 0)); } for (r in 0...numSides) { var nextR = (r + 1) % numSides; var v0 = capEndRingBase + r; var v1 = capEndRingBase + nextR; indices.push(capEndCenterIdx); indices.push(v0); indices.push(v1); } } } if (rawPositions.length == 0) return null; var maxdim = 0.0; for (p in rawPositions) { var ax = Math.abs(p.x); var ay = Math.abs(p.y); var az = Math.abs(p.z); if (ax > maxdim) maxdim = ax; if (ay > maxdim) maxdim = ay; if (az > maxdim) maxdim = az; } if (maxdim == 0) maxdim = 1.0; maxdim *= 2; var invdim = 1 / maxdim; var numVerts = rawPositions.length; var paa = new Int16Array(numVerts * 4); var naa = new Int16Array(numVerts * 2); var texa = new Int16Array(numVerts * 2); for (i in 0...numVerts) { var p = rawPositions[i]; var n = rawNormals[i]; var uv = rawUVs[i]; paa.set(i * 4, Std.int(p.x * 32767 * invdim)); paa.set(i * 4 + 1, Std.int(p.y * 32767 * invdim)); paa.set(i * 4 + 2, Std.int(p.z * 32767 * invdim)); naa.set(i * 2, Std.int(n.x * 32767)); naa.set(i * 2 + 1, Std.int(n.y * 32767)); paa.set(i * 4 + 3, Std.int(n.z * 32767)); texa.set(i * 2, Std.int(uv.x * 32767)); texa.set(i * 2 + 1, Std.int(uv.y * 32767)); } var indexArrays: Array = []; for (matIdx in indicesByMaterial.keys()) { var matIndices = indicesByMaterial.get(matIdx); var inda = new Uint32Array(matIndices.length); for (i in 0...matIndices.length) inda.set(i, matIndices[i]); indexArrays.push({ material: matIdx, values: inda }); } var pos: TVertexArray = { attrib: "pos", values: paa, data: "short4norm" }; var nor: TVertexArray = { attrib: "nor", values: naa, data: "short2norm" }; var tex: TVertexArray = { attrib: "tex", values: texa, data: "short2norm" }; var rawmesh: TMeshData = { name: data.name + "_mesh", sorting_index: 0, vertex_arrays: [pos, nor, tex], index_arrays: indexArrays, scale_pos: maxdim }; var md = new MeshData(rawmesh, function(d: MeshData) {}); md.geom.calculateAABB(); return md; } public function generateExtrudeMesh(width: Float = 0.1, thickness: Float = 0.1, factorStart: Float = 0.0, factorEnd: Float = 1.0, fillCaps: Bool = false): MeshData { if (data.splines == null || splinesLength == 0) return null; var rawPositions: Array = []; var rawNormals: Array = []; var rawUVs: Array = []; var indicesByMaterial = new Map>(); var halfW = width * 0.5; var halfH = thickness * 0.5; for (splineIndex in 0...splinesLength) { var spline = data.splines[splineIndex]; var matIdx = spline.material_index != null ? spline.material_index : 0; if (!indicesByMaterial.exists(matIdx)) indicesByMaterial.set(matIdx, []); var indices = indicesByMaterial.get(matIdx); var segments = spline.closed ? spline.points.length : spline.points.length - 1; var totalSteps = equidistantSamples > 0 ? equidistantSamples : Std.int(spline.resolution * segments); if (totalSteps < 1) totalSteps = 1; var startStep = Std.int(totalSteps * Math.max(0.0, Math.min(1.0, factorStart))); var endStep = Std.int(totalSteps * Math.max(0.0, Math.min(1.0, factorEnd))); var isClosedLoop = spline.closed && factorStart == 0.0 && factorEnd == 1.0; var numRings = endStep - startStep + 1; if (numRings < 2) continue; var baseIndex = rawPositions.length; var tangent = getTangent(factorStart, splineIndex); if (spline.closed && (factorStart == 0.0 || factorStart == 1.0)) { var tIn = getTangent(1.0, splineIndex); var tOut = getTangent(0.0, splineIndex); tangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); tangent.normalize(); } var normal = new Vec4(0, 1, 0); if (Math.abs(tangent.dot(normal)) > 0.9) normal.set(1, 0, 0); var binormal = new Vec4(); binormal.crossvecs(tangent, normal); binormal.normalize(); normal.crossvecs(binormal, tangent); normal.normalize(); var lastTangent = tangent.clone(); if (thickness == 0) { for (s in 0...numRings) { var stepIdx = startStep + s; var t = stepIdx / totalSteps; var p = getPoint(t, splineIndex); var currTangent = getTangent(t, splineIndex); if (spline.closed && (t == 0.0 || t == 1.0)) { var tIn = getTangent(1.0, splineIndex); var tOut = getTangent(0.0, splineIndex); currTangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); currTangent.normalize(); } var axisVec = new Vec4(); axisVec.crossvecs(lastTangent, currTangent); var dot = Math.max(-1.0, Math.min(1.0, lastTangent.dot(currTangent))); if (axisVec.length() > 0.00001) { axisVec.normalize(); var angle = Math.acos(dot); var q = new Quat(); q.fromAxisAngle(axisVec, angle); normal.applyQuat(q); binormal.applyQuat(q); } lastTangent = currTangent; var dirW = normal; var dirH = binormal; var vPos0 = new Vec4(p.x - dirW.x * halfW, p.y - dirW.y * halfW, p.z - dirW.z * halfW); var vPos1 = new Vec4(p.x + dirW.x * halfW, p.y + dirW.y * halfW, p.z + dirW.z * halfW); if (isClosedLoop && s == numRings - 1) { vPos0 = rawPositions[baseIndex].clone(); vPos1 = rawPositions[baseIndex + 1].clone(); } rawPositions.push(vPos0); rawNormals.push(dirH); rawUVs.push(new Vec4(t, 0.0, 0)); rawPositions.push(vPos1); rawNormals.push(dirH); rawUVs.push(new Vec4(t, 1.0, 0)); } for (s in 0...numRings - 1) { var v0 = baseIndex + s * 2; var v1 = baseIndex + s * 2 + 1; var v2 = baseIndex + (s + 1) * 2; var v3 = baseIndex + (s + 1) * 2 + 1; indices.push(v0); indices.push(v2); indices.push(v1); indices.push(v1); indices.push(v2); indices.push(v3); } } else { for (s in 0...numRings) { var stepIdx = startStep + s; var t = stepIdx / totalSteps; var p = getPoint(t, splineIndex); var currTangent = getTangent(t, splineIndex); if (spline.closed && (t == 0.0 || t == 1.0)) { var tIn = getTangent(1.0, splineIndex); var tOut = getTangent(0.0, splineIndex); currTangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); currTangent.normalize(); } var axisVec = new Vec4(); axisVec.crossvecs(lastTangent, currTangent); var dot = Math.max(-1.0, Math.min(1.0, lastTangent.dot(currTangent))); if (axisVec.length() > 0.00001) { axisVec.normalize(); var angle = Math.acos(dot); var q = new Quat(); q.fromAxisAngle(axisVec, angle); normal.applyQuat(q); binormal.applyQuat(q); } lastTangent = currTangent; var dirW = normal; var dirH = binormal; var offsets = [ new Vec4(-dirW.x * halfW - dirH.x * halfH, -dirW.y * halfW - dirH.y * halfH, -dirW.z * halfW - dirH.z * halfH), new Vec4( dirW.x * halfW - dirH.x * halfH, dirW.y * halfW - dirH.y * halfH, dirW.z * halfW - dirH.z * halfH), new Vec4( dirW.x * halfW + dirH.x * halfH, dirW.y * halfW + dirH.y * halfH, dirW.z * halfW + dirH.z * halfH), new Vec4(-dirW.x * halfW + dirH.x * halfH, -dirW.y * halfW + dirH.y * halfH, -dirW.z * halfW + dirH.z * halfH) ]; var normDirs = [ new Vec4(-dirW.x, -dirW.y, -dirW.z), new Vec4( dirW.x, dirW.y, dirW.z), new Vec4( dirH.x, dirH.y, dirH.z), new Vec4(-dirH.x, -dirH.y, -dirH.z) ]; for (i in 0...5) { var idx = i % 4; rawPositions.push(new Vec4(p.x + offsets[idx].x, p.y + offsets[idx].y, p.z + offsets[idx].z)); rawNormals.push(normDirs[idx]); rawUVs.push(new Vec4(t, i / 4.0, 0)); } } for (s in 0...numRings) { var nextS = s + 1; if (s == numRings - 1) continue; for (i in 0...4) { var v0 = baseIndex + s * 5 + i; var v1 = baseIndex + s * 5 + i + 1; var v2 = baseIndex + nextS * 5 + i; var v3 = baseIndex + nextS * 5 + i + 1; indices.push(v0); indices.push(v2); indices.push(v1); indices.push(v1); indices.push(v2); indices.push(v3); } } var isPartial = factorStart > 0.0 || factorEnd < 1.0; if (fillCaps && (!spline.closed || isPartial)) { var tStart = factorStart; var startTangent = getTangent(tStart, splineIndex); var capStartNormal = new Vec4(-startTangent.x, -startTangent.y, -startTangent.z); var pStart = new Vec4(0, 0, 0); for (r in 0...4) { var pos = rawPositions[baseIndex + r]; pStart.add(pos); } pStart.mult(0.25); var capStartCenterIdx = rawPositions.length; rawPositions.push(pStart); rawNormals.push(capStartNormal); rawUVs.push(new Vec4(0.5, 0.5, 0)); var capStartRingBase = rawPositions.length; for (r in 0...4) { var origIdx = baseIndex + r; var angle = (r / 4.0) * Math.PI * 2.0; var u = 0.5 + 0.5 * Math.cos(angle); var v = 0.5 + 0.5 * Math.sin(angle); rawPositions.push(rawPositions[origIdx].clone()); rawNormals.push(capStartNormal); rawUVs.push(new Vec4(u, v, 0)); } for (r in 0...4) { var nextR = (r + 1) % 4; var v0 = capStartRingBase + r; var v1 = capStartRingBase + nextR; indices.push(capStartCenterIdx); indices.push(v1); indices.push(v0); } var tEnd = factorEnd; var endTangent = getTangent(tEnd, splineIndex); var capEndNormal = endTangent.clone(); var lastRingBase = baseIndex + (numRings - 1) * 5; var pEnd = new Vec4(0, 0, 0); for (r in 0...4) { var pos = rawPositions[lastRingBase + r]; pEnd.add(pos); } pEnd.mult(0.25); var capEndCenterIdx = rawPositions.length; rawPositions.push(pEnd); rawNormals.push(capEndNormal); rawUVs.push(new Vec4(0.5, 0.5, 0)); var capEndRingBase = rawPositions.length; for (r in 0...4) { var origIdx = lastRingBase + r; var angle = (r / 4.0) * Math.PI * 2.0; var u = 0.5 + 0.5 * Math.cos(angle); var v = 0.5 + 0.5 * Math.sin(angle); rawPositions.push(rawPositions[origIdx].clone()); rawNormals.push(capEndNormal); rawUVs.push(new Vec4(u, v, 0)); } for (r in 0...4) { var nextR = (r + 1) % 4; var v0 = capEndRingBase + r; var v1 = capEndRingBase + nextR; indices.push(capEndCenterIdx); indices.push(v0); indices.push(v1); } } } } if (rawPositions.length == 0) return null; var maxdim = 0.0; for (p in rawPositions) { var ax = Math.abs(p.x); var ay = Math.abs(p.y); var az = Math.abs(p.z); if (ax > maxdim) maxdim = ax; if (ay > maxdim) maxdim = ay; if (az > maxdim) maxdim = az; } if (maxdim == 0) maxdim = 1.0; maxdim *= 2; var invdim = 1 / maxdim; var numVerts = rawPositions.length; var paa = new Int16Array(numVerts * 4); var naa = new Int16Array(numVerts * 2); var texa = new Int16Array(numVerts * 2); for (i in 0...numVerts) { var p = rawPositions[i]; var n = rawNormals[i]; var uv = rawUVs[i]; paa.set(i * 4, Std.int(p.x * 32767 * invdim)); paa.set(i * 4 + 1, Std.int(p.y * 32767 * invdim)); paa.set(i * 4 + 2, Std.int(p.z * 32767 * invdim)); naa.set(i * 2, Std.int(n.x * 32767)); naa.set(i * 2 + 1, Std.int(n.y * 32767)); paa.set(i * 4 + 3, Std.int(n.z * 32767)); texa.set(i * 2, Std.int(uv.x * 32767)); texa.set(i * 2 + 1, Std.int(uv.y * 32767)); } var indexArrays: Array = []; for (matIdx in indicesByMaterial.keys()) { var matIndices = indicesByMaterial.get(matIdx); var inda = new Uint32Array(matIndices.length); for (i in 0...matIndices.length) inda.set(i, matIndices[i]); indexArrays.push({ material: matIdx, values: inda }); } var pos: TVertexArray = { attrib: "pos", values: paa, data: "short4norm" }; var nor: TVertexArray = { attrib: "nor", values: naa, data: "short2norm" }; var tex: TVertexArray = { attrib: "tex", values: texa, data: "short2norm" }; var rawmesh: TMeshData = { name: data.name + "_mesh", sorting_index: 0, vertex_arrays: [pos, nor, tex], index_arrays: indexArrays, scale_pos: maxdim }; var md = new MeshData(rawmesh, function(d: MeshData) {}); md.geom.calculateAABB(); return md; } public function generateDeformedMesh(baseMesh: MeshData, splineIndex = -1, forwardAxis: String = "X", repetitions: Int, factorStart: Float = 0.0, factorEnd: Float = 1.0): MeshData { if (data.splines == null || splinesLength == 0 || baseMesh == null || repetitions <= 0) return null; var rawPositions: Array = []; var rawNormals: Array = []; var rawUVs: Array = []; var indicesByMaterial = new Map>(); var baseRaw = baseMesh.raw; var basePos: Int16Array = null; var baseNor: Int16Array = null; var baseTex: Int16Array = null; for (va in baseRaw.vertex_arrays) { if (va.attrib == "pos") basePos = va.values; else if (va.attrib == "nor") baseNor = va.values; else if (va.attrib == "tex") baseTex = va.values; } if (basePos == null) return null; var baseVertCount = Std.int(basePos.length / 4); var baseScale = baseRaw.scale_pos; var minVal = 1e10; var maxVal = -1e10; var unpackedPos = new Array(); var unpackedNor = new Array(); var unpackedTex = new Array(); for (i in 0...baseVertCount) { var px = (basePos.get(i * 4) / 32767) * baseScale; var py = (basePos.get(i * 4 + 1) / 32767) * baseScale; var pz = (basePos.get(i * 4 + 2) / 32767) * baseScale; unpackedPos.push(new Vec4(px, py, pz)); var fval = 0.0; switch (forwardAxis) { case "X": fval = px; case "-X": fval = -px; case "Y": fval = py; case "-Y": fval = -py; case "Z": fval = pz; case "-Z": fval = -pz; default: fval = px; } if (fval < minVal) minVal = fval; if (fval > maxVal) maxVal = fval; if (baseNor != null) { var nx = baseNor.get(i * 2) / 32767; var ny = baseNor.get(i * 2 + 1) / 32767; var nz = basePos.get(i * 4 + 3) / 32767; unpackedNor.push(new Vec4(nx, ny, nz)); } else { unpackedNor.push(new Vec4(0, 0, 1)); } if (baseTex != null) { var tu = baseTex.get(i * 2) / 32767; var tv = baseTex.get(i * 2 + 1) / 32767; unpackedTex.push(new Vec4(tu, tv, 0)); } else { unpackedTex.push(new Vec4(0, 0, 0)); } } var sizeVal = maxVal - minVal; if (sizeVal <= 0.00001) sizeVal = 1.0; var startIdx = splineIndex == -1 ? 0 : splineIndex; var endIdx = splineIndex == -1 ? splinesLength : splineIndex + 1; for (sIdx in startIdx...endIdx) { var spline = data.splines[sIdx]; var segments = spline.closed ? spline.points.length : spline.points.length - 1; var frameCount = equidistantSamples > 0 ? equidistantSamples : Std.int(spline.resolution * segments); var framesNor = new Array(); var framesBin = new Array(); var framesTan = new Array(); var startTangent = getTangent(0.0, sIdx); if (spline.closed) { var tIn = getTangent(1.0, sIdx); var tOut = getTangent(0.0, sIdx); startTangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); startTangent.normalize(); } var _z = new Vec4(0, 0, 1); if (Math.abs(startTangent.dot(_z)) > 0.9999) { _z.set(0, 1, 0); } var _r = new Vec4(); _r.crossvecs(startTangent, _z).normalize(); var _u = new Vec4(); _u.crossvecs(_r, startTangent).normalize(); var normal = new Vec4(-_r.x, -_r.y, -_r.z); var binormal = new Vec4(_u.x, _u.y, _u.z); var lastTangent = startTangent.clone(); for (i in 0...frameCount + 1) { var t = i / frameCount; var currTangent = getTangent(t, sIdx); if (spline.closed && (t == 0.0 || t == 1.0)) { var tIn = getTangent(1.0, sIdx); var tOut = getTangent(0.0, sIdx); currTangent.set(tIn.x + tOut.x, tIn.y + tOut.y, tIn.z + tOut.z); currTangent.normalize(); } var axis = new Vec4(); axis.crossvecs(lastTangent, currTangent); var dot = Math.max(-1.0, Math.min(1.0, lastTangent.dot(currTangent))); if (axis.length() > 0.00001) { axis.normalize(); var angle = Math.acos(dot); var q = new Quat(); q.fromAxisAngle(axis, angle); normal.applyQuat(q); binormal.applyQuat(q); } lastTangent = currTangent.clone(); framesNor.push(normal.clone()); framesBin.push(binormal.clone()); framesTan.push(currTangent.clone()); } if (spline.closed) { var endDot = Math.max(-1.0, Math.min(1.0, framesNor[0].dot(framesNor[frameCount]))); var endAngle = Math.acos(endDot); var crossTest = new Vec4(); crossTest.crossvecs(framesNor[0], framesNor[frameCount]); if (framesTan[0].dot(crossTest) < 0) { endAngle = -endAngle; } for (i in 0...frameCount + 1) { var t = i / frameCount; var qFix = new Quat(); qFix.fromAxisAngle(framesTan[i], -endAngle * t); framesNor[i].applyQuat(qFix); framesBin[i].applyQuat(qFix); } } for (rep in 0...repetitions) { var baseIndex = rawPositions.length; var repTStart = factorStart + (rep / repetitions) * (factorEnd - factorStart); var repTEnd = factorStart + ((rep + 1) / repetitions) * (factorEnd - factorStart); for (i in 0...baseVertCount) { var p = unpackedPos[i]; var n = unpackedNor[i]; var uv = unpackedTex[i]; var fval = 0.0; var rval = 0.0; var uval = 0.0; var nfval = 0.0; var nrval = 0.0; var nuval = 0.0; switch (forwardAxis) { case "X": fval = p.x; rval = p.y; uval = p.z; nfval = n.x; nrval = n.y; nuval = n.z; case "-X": fval = -p.x; rval = -p.y; uval = p.z; nfval = -n.x; nrval = -n.y; nuval = n.z; case "Y": fval = p.y; rval = -p.x; uval = p.z; nfval = n.y; nrval = -n.x; nuval = n.z; case "-Y": fval = -p.y; rval = p.x; uval = p.z; nfval = -n.y; nrval = n.x; nuval = n.z; case "Z": fval = p.z; rval = -p.x; uval = -p.y; nfval = n.z; nrval = -n.x; nuval = -n.y; case "-Z": fval = -p.z; rval = -p.x; uval = p.y; nfval = -n.z; nrval = -n.x; nuval = n.y; default: fval = p.x; rval = p.y; uval = p.z; nfval = n.x; nrval = n.y; nuval = n.z; } var localT = (fval - minVal) / sizeVal; var curveT = repTStart + localT * (repTEnd - repTStart); if (curveT < 0) curveT = 0; if (curveT > 1) curveT = 1; var fIdx = curveT * frameCount; var i0 = Std.int(fIdx); var i1 = i0 + 1; if (i1 > frameCount) i1 = frameCount; var blend = fIdx - i0; var n0 = framesNor[i0]; var n1 = framesNor[i1]; var b0 = framesBin[i0]; var b1 = framesBin[i1]; var t0 = framesTan[i0]; var t1 = framesTan[i1]; var normalVec = new Vec4( n0.x + (n1.x - n0.x) * blend, n0.y + (n1.y - n0.y) * blend, n0.z + (n1.z - n0.z) * blend ); normalVec.normalize(); var binormalVec = new Vec4( b0.x + (b1.x - b0.x) * blend, b0.y + (b1.y - b0.y) * blend, b0.z + (b1.z - b0.z) * blend ); binormalVec.normalize(); var tangentVec = new Vec4( t0.x + (t1.x - t0.x) * blend, t0.y + (t1.y - t0.y) * blend, t0.z + (t1.z - t0.z) * blend ); tangentVec.normalize(); var curvePos = getPoint(curveT, sIdx); var finalPos = new Vec4( curvePos.x + normalVec.x * rval + binormalVec.x * uval, curvePos.y + normalVec.y * rval + binormalVec.y * uval, curvePos.z + normalVec.z * rval + binormalVec.z * uval ); var finalNor = new Vec4( normalVec.x * nrval + binormalVec.x * nuval + tangentVec.x * nfval, normalVec.y * nrval + binormalVec.y * nuval + tangentVec.y * nfval, normalVec.z * nrval + binormalVec.z * nuval + tangentVec.z * nfval ); finalNor.normalize(); rawPositions.push(finalPos); rawNormals.push(finalNor); rawUVs.push(new Vec4(uv.x, uv.y, 0)); } for (ia in baseRaw.index_arrays) { var mat = ia.material; if (!indicesByMaterial.exists(mat)) indicesByMaterial.set(mat, []); var destIndices = indicesByMaterial.get(mat); var srcIndices = ia.values; for (i in 0...srcIndices.length) { destIndices.push(baseIndex + srcIndices.get(i)); } } } } var maxdim = 0.0; for (p in rawPositions) { var ax = Math.abs(p.x); var ay = Math.abs(p.y); var az = Math.abs(p.z); if (ax > maxdim) maxdim = ax; if (ay > maxdim) maxdim = ay; if (az > maxdim) maxdim = az; } if (maxdim == 0) maxdim = 1.0; maxdim *= 2; var invdim = 1 / maxdim; var numVerts = rawPositions.length; var paa = new Int16Array(numVerts * 4); var naa = new Int16Array(numVerts * 2); var texa = new Int16Array(numVerts * 2); for (i in 0...numVerts) { var p = rawPositions[i]; var n = rawNormals[i]; var uv = rawUVs[i]; paa.set(i * 4, Std.int(p.x * 32767 * invdim)); paa.set(i * 4 + 1, Std.int(p.y * 32767 * invdim)); paa.set(i * 4 + 2, Std.int(p.z * 32767 * invdim)); naa.set(i * 2, Std.int(n.x * 32767)); naa.set(i * 2 + 1, Std.int(n.y * 32767)); paa.set(i * 4 + 3, Std.int(n.z * 32767)); texa.set(i * 2, Std.int(uv.x * 32767)); texa.set(i * 2 + 1, Std.int(uv.y * 32767)); } var indexArrays: Array = []; for (matIdx in indicesByMaterial.keys()) { var matIndices = indicesByMaterial.get(matIdx); var inda = new Uint32Array(matIndices.length); for (i in 0...matIndices.length) inda.set(i, matIndices[i]); indexArrays.push({ material: matIdx, values: inda }); } var posVA: TVertexArray = { attrib: "pos", values: paa, data: "short4norm" }; var norVA: TVertexArray = { attrib: "nor", values: naa, data: "short2norm" }; var texVA: TVertexArray = { attrib: "tex", values: texa, data: "short2norm" }; var rawmesh: TMeshData = { name: data.name + "_mesh", sorting_index: 0, vertex_arrays: [posVA, norVA, texVA], index_arrays: indexArrays, scale_pos: maxdim }; var md = new MeshData(rawmesh, function(d: MeshData) {}); md.geom.calculateAABB(); return md; } }