Skip to content

Commit

Permalink
Adapt to upstream gltf schema changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Shinmera committed Jan 20, 2025
1 parent f3850c1 commit 3fd57c7
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 72 deletions.
85 changes: 46 additions & 39 deletions formats/gltf/optimize.lisp
Original file line number Diff line number Diff line change
@@ -1,57 +1,64 @@
(in-package #:org.shirakumo.fraf.trial.gltf)

(defun add-convex-shape (gltf vertices faces)
(let* ((primitive (gltf:make-mesh-primitive gltf vertices faces '(:position)))
(mesh (gltf:make-indexed 'gltf:mesh gltf :primitives (vector primitive))))
(gltf:make-indexed 'gltf:mesh-shape gltf :mesh mesh :kind "mesh" :convex-p T)))

(defun push-convex-shape (base-node shape)
(let* ((collider (make-instance 'gltf:collider :collision-filter (gltf:collision-filter (gltf:collider base-node))
:physics-material (gltf:physics-material (gltf:collider base-node))
:shape shape
:gltf (gltf:gltf base-node)))
(child (gltf:make-indexed 'gltf:node base-node :collider collider :virtual-p T)))
(gltf:push-child child base-node)))
(defun add-convex-mesh (gltf primitives &optional name)
(flet ((make-primitive (geometry)
(gltf:make-mesh-primitive gltf (car geometry) (cdr geometry) '(:position))))
(gltf:make-indexed 'gltf:mesh gltf
:name name
:primitives (map 'vector #'make-primitive primitives))))

(defmethod optimize-model (file (type (eql :glb)) &rest args)
(apply #'optimize-model file :gltf args))

(defun shape-optimizable-p (shape)
(and (typep shape 'gltf:mesh-shape)
(not (gltf:convex-p shape))))

(defmethod optimize-model (file (type (eql :gltf)) &rest args &key (output file) &allow-other-keys)
(let ((decomposition-args (remf* args :output))
(shape-table (make-hash-table :test 'eql))
(mesh-table (make-hash-table :test 'eql))
(work-done-p NIL))
(trial:with-tempfile (tmp :type (pathname-type file))
(gltf:with-gltf (gltf file)
;; Rewrite mesh shapes to multiple new shapes.
;; TODO: if original mesh has no other refs anywhere, remove it
(loop for shape across (gltf:shapes gltf)
do (when (and (shape-optimizable-p shape)
;; Only bother decomposing it if it's actually referenced anywhere.
(loop for node across (gltf:nodes gltf)
thereis (and (gltf:collider node) (eql shape (gltf:shape (gltf:collider node))))))
(let* ((primitives (gltf:primitives (gltf:mesh shape)))
(mesh (load-primitive (aref primitives 0)))
(verts (reordered-vertex-data mesh '(location)))
(hulls (apply #'trial::decompose-to-convex verts (faces mesh) decomposition-args)))
(setf (gethash shape shape-table)
(loop for (verts . faces) across hulls
collect (add-convex-shape gltf verts faces))))))
;; Rewrite nodes with refs to mesh colliders to have child nodes for
;; all decomposed hulls.
(loop for node across (gltf:nodes gltf)
do (when (and (gltf:collider node) (shape-optimizable-p (gltf:shape (gltf:collider node))))
(loop for shape in (gethash (gltf:shape (gltf:collider node)) shape-table)
do (push-convex-shape node shape))
(setf (gltf:collider node) NIL)
;; Clear the extension, too. Ideally this would be done by the library already.
(remhash "collider" (gethash "KHR_physics_rigid_bodies" (gltf:extensions node)))
(setf work-done-p T)))
for collider = (gltf:collider node)
do (when collider
(let* ((collider (gltf:collider node))
(geometry (gltf:geometry collider)))
(when (gltf:node geometry)
(let ((new (gethash (gltf:mesh (gltf:node geometry)) mesh-table)))
(unless new
(let* ((node (gltf:node geometry))
(mesh (gltf:mesh node))
(meshes (loop for primitive across (gltf:primitives mesh)
for mesh = (load-primitive primitive)
collect (cons (reordered-vertex-data mesh '(location)) (faces mesh))))
(meshes (cond ((gltf:convex-p geometry)
(v:info :trial.gltf "Re-hulling ~a" mesh)
(loop for (vertices) in meshes
collect (multiple-value-bind (vertices faces) (org.shirakumo.fraf.quickhull:convex-hull vertices)
(cons vertices faces))))
(T
(v:info :trial.gltf "Decomposing ~a" mesh)
(loop for (vertices . faces) in meshes
nconc (coerce (apply #'trial::decompose-to-convex vertices faces decomposition-args) 'list)))))
(new-mesh (add-convex-mesh gltf meshes (format NIL "~a/~:[decomposed~;rehulled~]"
(gltf:name mesh) (gltf:convex-p geometry)))))
(v:info :trial.gltf "Creating new mesh ~a" new-mesh)
(setf new (gltf:make-indexed 'gltf:node node :mesh new-mesh
:name (gltf:name new-mesh)
:virtual-p T
:matrix (gltf:matrix node)
:rotation (gltf:rotation node)
:scale (gltf:scale node)
:translation (gltf:translation node)))
(setf (gethash mesh mesh-table) new)))
(v:info :trial.gltf "Updating ~a to point to ~a" node new)
(setf (gltf:node geometry) new)
(setf (gltf:convex-p geometry) T)
(when (gltf:extensions node)
(remhash "KHR_physics_rigid_bodies" (gltf:extensions node)))
(setf work-done-p T))))))
(when work-done-p
(gltf:serialize gltf tmp)))
(when work-done-p
(when (and work-done-p output)
;; FIXME: this does not work correctly if gltf serialises to multiple files.
(org.shirakumo.filesystem-utils:rename-file* tmp output)))))
66 changes: 33 additions & 33 deletions formats/gltf/physics.lisp
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
(in-package #:org.shirakumo.fraf.trial.gltf)

(defun load-shape (shape model &rest args)
(defun load-shape (geometry model &rest args)
(flet ((ensure-mesh (mesh)
(or (find-mesh (gltf-name mesh) model NIL)
(first (load-mesh mesh model)))))
(etypecase shape
(gltf:sphere-shape
(apply #'trial:make-sphere :radius (float (gltf:radius shape) 0f0)
args))
(gltf:box-shape
(apply #'trial:make-box :bsize (nv* (to-vec (gltf:size shape)) 0.5)
args))
(gltf:capsule-shape
(apply #'trial:make-pill :height (float (* 0.5 (gltf:height shape)) 0f0)
:radius (max (float (gltf:radius-top shape) 0f0)
(float (gltf:radius-bottom shape) 0f0))
args))
(gltf:cylinder-shape
(apply (cond ((= 0 (gltf:radius-top shape))
#'trial:make-cone)
(T
#'trial:make-cylinder))
:height (float (* 0.5 (gltf:height shape)) 0f0)
:radius (max (float (gltf:radius-top shape) 0f0)
(float (gltf:radius-bottom shape) 0f0))
args))
(gltf:mesh-shape
(let ((mesh (ensure-mesh (gltf:mesh shape))))
(apply (if (gltf:convex-p shape)
#'trial::make-maybe-optimized-convex-mesh
#'trial:make-general-mesh)
:vertices (trial:reordered-vertex-data mesh '(trial:location))
:faces (trial::simplify (trial:faces mesh) '(unsigned-byte 16))
args))))))
(let ((shape (gltf:shape geometry)))
(etypecase shape
(gltf:sphere-shape
(apply #'trial:make-sphere :radius (float (gltf:radius shape) 0f0)
args))
(gltf:box-shape
(apply #'trial:make-box :bsize (nv* (to-vec (gltf:size shape)) 0.5)
args))
(gltf:capsule-shape
(apply #'trial:make-pill :height (float (* 0.5 (gltf:height shape)) 0f0)
:radius (max (float (gltf:radius-top shape) 0f0)
(float (gltf:radius-bottom shape) 0f0))
args))
(gltf:cylinder-shape
(apply (cond ((= 0 (gltf:radius-top shape))
#'trial:make-cone)
(T
#'trial:make-cylinder))
:height (float (* 0.5 (gltf:height shape)) 0f0)
:radius (max (float (gltf:radius-top shape) 0f0)
(float (gltf:radius-bottom shape) 0f0))
args))
(null
(let ((mesh (ensure-mesh (gltf:mesh (gltf:node geometry)))))
(apply (if (gltf:convex-p geometry)
#'trial::make-maybe-optimized-convex-mesh
#'trial:make-general-mesh)
:vertices (trial:reordered-vertex-data mesh '(trial:location))
:faces (trial::simplify (trial:faces mesh) '(unsigned-byte 16))
args)))))))

(defvar *physics-material-cache* (make-hash-table :test 'equal))
(defun physics-material-instance (material)
Expand Down Expand Up @@ -72,8 +73,7 @@
(setf material (physics-material-instance (gltf:physics-material collider))))
(when (gltf:collision-filter collider)
(setf mask (collision-filter-mask (gltf:collision-filter collider))))
(let ((primitive (load-shape (gltf:shape collider) model
:local-transform (tmat tf))))
(let ((primitive (load-shape (gltf:geometry collider) model :local-transform (tmat tf))))
(setf (trial:primitive-collision-mask primitive) mask)
(setf (trial:primitive-material primitive) material)
(vector-push-extend primitive primitives)))
Expand Down Expand Up @@ -127,7 +127,7 @@
(basic-entity 'basic-physics-entity)
(animated-entity 'animated-physics-entity)
(T 'trigger-volume))))
(let ((shape (load-shape (gltf:shape (gltf:trigger node)) model)))
(let ((shape (load-shape (gltf:geometry (gltf:trigger node)) model)))
(setf (trial:primitive-collision-mask shape) (collision-filter-mask (gltf:collision-filter (gltf:trigger node))))
(setf (trial:physics-primitives child) shape)
(with-simple-restart (continue "Ignore the trigger translation")
Expand Down

0 comments on commit 3fd57c7

Please sign in to comment.