KHR_lights_punctual.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { SpotLight } from "babylonjs/Lights/spotLight";
  2. import { Nullable } from "babylonjs/types";
  3. import { Vector3, Quaternion } from "babylonjs/Maths/math.vector";
  4. import { Color3 } from "babylonjs/Maths/math.color";
  5. import { Light } from "babylonjs/Lights/light";
  6. import { DirectionalLight } from "babylonjs/Lights/directionalLight"
  7. import { Node } from "babylonjs/node";
  8. import { ShadowLight } from "babylonjs/Lights/shadowLight";
  9. import { IChildRootProperty } from "babylonjs-gltf2interface";
  10. import { INode } from "babylonjs-gltf2interface";
  11. import { IGLTFExporterExtensionV2 } from "../glTFExporterExtension";
  12. import { _Exporter } from "../glTFExporter";
  13. import { Logger } from "babylonjs/Misc/logger";
  14. import { _GLTFUtilities } from "../glTFUtilities";
  15. const NAME = "KHR_lights_punctual";
  16. enum LightType {
  17. DIRECTIONAL = "directional",
  18. POINT = "point",
  19. SPOT = "spot"
  20. }
  21. interface ILightReference {
  22. light: number;
  23. }
  24. interface ILight extends IChildRootProperty {
  25. type: LightType;
  26. color?: number[];
  27. intensity?: number;
  28. range?: number;
  29. spot?: {
  30. innerConeAngle?: number;
  31. outerConeAngle?: number;
  32. };
  33. }
  34. interface ILights {
  35. lights: ILight[];
  36. }
  37. /**
  38. * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md)
  39. */
  40. export class KHR_lights_punctual implements IGLTFExporterExtensionV2 {
  41. /** The name of this extension. */
  42. public readonly name = NAME;
  43. /** Defines whether this extension is enabled. */
  44. public enabled = true;
  45. /** Defines whether this extension is required */
  46. public required = false;
  47. /** Reference to the glTF exporter */
  48. private _exporter: _Exporter;
  49. private _lights: ILights;
  50. /** @hidden */
  51. constructor(exporter: _Exporter) {
  52. this._exporter = exporter;
  53. }
  54. /** @hidden */
  55. public dispose() {
  56. delete this._lights;
  57. }
  58. /** @hidden */
  59. public get wasUsed() {
  60. return !!this._lights;
  61. }
  62. /** @hidden */
  63. public onExporting(): void {
  64. this._exporter!._glTF.extensions![NAME] = this._lights;
  65. }
  66. /**
  67. * Define this method to modify the default behavior when exporting a node
  68. * @param context The context when exporting the node
  69. * @param node glTF node
  70. * @param babylonNode BabylonJS node
  71. * @param nodeMap Node mapping of unique id to glTF node index
  72. * @returns nullable INode promise
  73. */
  74. public postExportNodeAsync(context: string, node: Nullable<INode>, babylonNode: Node, nodeMap?: {[key: number]: number}): Promise<Nullable<INode>> {
  75. return new Promise((resolve, reject) => {
  76. if (node && babylonNode instanceof ShadowLight) {
  77. const babylonLight: ShadowLight = babylonNode;
  78. let light: ILight;
  79. const lightType = (
  80. babylonLight.getTypeID() == Light.LIGHTTYPEID_POINTLIGHT ? LightType.POINT : (
  81. babylonLight.getTypeID() == Light.LIGHTTYPEID_DIRECTIONALLIGHT ? LightType.DIRECTIONAL : (
  82. babylonLight.getTypeID() == Light.LIGHTTYPEID_SPOTLIGHT ? LightType.SPOT : null
  83. )));
  84. if (lightType == null) {
  85. Logger.Warn(`${context}: Light ${babylonLight.name} is not supported in ${NAME}`);
  86. }
  87. else {
  88. const lightPosition = babylonLight.position.clone();
  89. let convertToRightHandedSystem = this._exporter._convertToRightHandedSystemMap[babylonNode.uniqueId];
  90. if (!lightPosition.equals(Vector3.Zero())) {
  91. if (convertToRightHandedSystem) {
  92. _GLTFUtilities._GetRightHandedPositionVector3FromRef(lightPosition);
  93. }
  94. node.translation = lightPosition.asArray();
  95. }
  96. if (lightType !== LightType.POINT) {
  97. const localAxis = babylonLight.direction;
  98. const yaw = -Math.atan2(localAxis.z * (this._exporter._babylonScene.useRightHandedSystem ? -1 : 1), localAxis.x) + Math.PI / 2;
  99. const len = Math.sqrt(localAxis.x * localAxis.x + localAxis.z * localAxis.z);
  100. const pitch = -Math.atan2(localAxis.y, len);
  101. const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw, pitch, 0);
  102. if (convertToRightHandedSystem) {
  103. _GLTFUtilities._GetRightHandedQuaternionFromRef(lightRotationQuaternion);
  104. }
  105. if (!lightRotationQuaternion.equals(Quaternion.Identity())) {
  106. node.rotation = lightRotationQuaternion.asArray();
  107. }
  108. }
  109. if (babylonLight.falloffType !== Light.FALLOFF_GLTF) {
  110. Logger.Warn(`${context}: Light falloff for ${babylonLight.name} does not match the ${NAME} specification!`);
  111. }
  112. light = {
  113. type: lightType
  114. };
  115. if (!babylonLight.diffuse.equals(Color3.White())) {
  116. light.color = babylonLight.diffuse.asArray();
  117. }
  118. if (babylonLight.intensity !== 1.0) {
  119. light.intensity = babylonLight.intensity;
  120. }
  121. if (babylonLight.range !== Number.MAX_VALUE) {
  122. light.range = babylonLight.range;
  123. }
  124. if (lightType === LightType.SPOT) {
  125. const babylonSpotLight = babylonLight as SpotLight;
  126. if (babylonSpotLight.angle !== Math.PI / 2.0) {
  127. if (light.spot == null) {
  128. light.spot = {};
  129. }
  130. light.spot.outerConeAngle = babylonSpotLight.angle / 2.0;
  131. }
  132. if (babylonSpotLight.innerAngle !== 0) {
  133. if (light.spot == null) {
  134. light.spot = {};
  135. }
  136. light.spot.innerConeAngle = babylonSpotLight.innerAngle / 2.0;
  137. }
  138. }
  139. if (this._lights == null) {
  140. this._lights = {
  141. lights: []
  142. };
  143. }
  144. this._lights.lights.push(light);
  145. const lightReference: ILightReference = {
  146. light: this._lights.lights.length - 1
  147. };
  148. // Avoid duplicating the Light's parent node if possible.
  149. let parentBabylonNode = babylonNode.parent;
  150. if (parentBabylonNode && parentBabylonNode.getChildren().length == 1) {
  151. let parentNode = this._exporter._nodes[nodeMap![parentBabylonNode.uniqueId]];
  152. if (parentNode) {
  153. let parentNodeLocalMatrix = TmpVectors.Matrix[0];
  154. let parentInvertNodeLocalMatrix = TmpVectors.Matrix[1];
  155. let parentNodeLocalTranslation = parentNode.translation ? new Vector3(parentNode.translation[0], parentNode.translation[1], parentNode.translation[2]) : Vector3.Zero();
  156. let parentNodeLocalRotation = parentNode.rotation ? new Quaternion(parentNode.rotation[0], parentNode.rotation[1], parentNode.rotation[2], parentNode.rotation[3]) : Quaternion.Identity();
  157. let parentNodeLocalScale = parentNode.scale ? new Vector3(parentNode.scale[0], parentNode.scale[1], parentNode.scale[2]) : Vector3.One();
  158. Matrix.ComposeToRef(parentNodeLocalScale, parentNodeLocalRotation, parentNodeLocalTranslation, parentNodeLocalMatrix);
  159. parentNodeLocalMatrix.invertToRef(parentInvertNodeLocalMatrix);
  160. // Convert light local matrix to local matrix relative to grandparent, facing -Z
  161. let lightLocalMatrix = TmpVectors.Matrix[2];
  162. let nodeLocalTranslation = node.translation ? new Vector3(node.translation[0], node.translation[1], node.translation[2]) : Vector3.Zero();
  163. // Undo directional light positional offset
  164. if (babylonLight instanceof DirectionalLight) {
  165. nodeLocalTranslation.subtractInPlace(this._exporter._babylonScene.useRightHandedSystem ? babylonLight.direction : _GLTFUtilities._GetRightHandedPositionVector3(babylonLight.direction));
  166. }
  167. let nodeLocalRotation = this._exporter._babylonScene.useRightHandedSystem ? Quaternion.Identity() : new Quaternion(0, 1, 0, 0);
  168. if (node.rotation) {
  169. nodeLocalRotation.multiplyInPlace(new Quaternion(node.rotation[0], node.rotation[1], node.rotation[2], node.rotation[3]));
  170. }
  171. let nodeLocalScale = node.scale ? new Vector3(node.scale[0], node.scale[1], node.scale[2]) : Vector3.One();
  172. Matrix.ComposeToRef(nodeLocalScale, nodeLocalRotation, nodeLocalTranslation, lightLocalMatrix);
  173. lightLocalMatrix.multiplyToRef(parentInvertNodeLocalMatrix, lightLocalMatrix);
  174. let parentNewScale = TmpVectors.Vector3[0];
  175. let parentNewRotationQuaternion = TmpVectors.Quaternion[0];
  176. let parentNewTranslation = TmpVectors.Vector3[1];
  177. lightLocalMatrix.decompose(parentNewScale, parentNewRotationQuaternion, parentNewTranslation);
  178. parentNode.scale = parentNewScale.asArray();
  179. parentNode.rotation = parentNewRotationQuaternion.asArray();
  180. parentNode.translation = parentNewTranslation.asArray();
  181. if (parentNode.extensions == null) {
  182. parentNode.extensions = {};
  183. }
  184. parentNode.extensions[NAME] = lightReference;
  185. // Do not export the original node
  186. resolve(undefined);
  187. return;
  188. }
  189. }
  190. if (node.extensions == null) {
  191. node.extensions = {};
  192. }
  193. node.extensions[NAME] = lightReference;
  194. }
  195. }
  196. resolve(node);
  197. });
  198. }
  199. }
  200. _Exporter.RegisterExtension(NAME, (exporter) => new KHR_lights_punctual(exporter));