All Downloads are FREE. Search and download functionalities are using the official Maven repository.

simplex3d.engine.graphics.Attributes.scala Maven / Gradle / Ivy

/*
 * Simplex3dEngine - Core Module
 * Copyright (C) 2011, Aleksey Nikiforov
 *
 * This file is part of Simplex3dEngine.
 *
 * Simplex3dEngine is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Simplex3dEngine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 */

package simplex3d.engine
package graphics

import java.util.WeakHashMap
import scala.reflect._
import scala.annotation.unchecked._
import scala.collection._
import scala.collection.mutable.ArrayBuffer
import simplex3d.math.types._
import simplex3d.data.extension._
import simplex3d.data._
import simplex3d.engine.util._


final class Attributes[F <: Format, +R <: Raw] private[engine] (
  @transient private val accessible: ReadDataView[F, _ <: R],
  private val linked: DirectSrc
) {

  private[engine] var shared: AttributesSharedState = null
  
  private[this] val bindings = if (isWritable) new WeakHashMap[AttributeBinding[_, _], Object] else null
  private[this] def notifyBindings() {
    val iter = bindings.keySet.iterator()
    while(iter.hasNext) {
      val binding = iter.next()
      binding.signalDataChanges()
    }
  }
  private[engine] def register(binding: AttributeBinding[_, _]) {
    bindings.put(binding, null)
  }
  private[engine] def unregister(binding: AttributeBinding[_, _]) {
    bindings.remove(binding)
  }
  
  { // Consistency check.
    var count = 0
    if (accessible != null) count += 1
    if (linked != null) count += 1
    
    if (count != 1) throw new IllegalArgumentException(
      "Data source must not be null.")
  }
  
  def sharedState = shared
  def isAccessible = (accessible != null)
  def isWritable = (isAccessible && !accessible.isReadOnly)
  
  def read: ReadDataView[F, R] = {
    if (isAccessible) accessible.asReadOnly()
    else throw new IllegalAccessException("Attributes are not accessible.")
  }
  
  def write: DataView[F, R] = write(0, src.size)
  
  def write(first: Int, count: Int): DataView[F, R] = {
    if (isWritable) {
      if (first < 0) throw new IllegalArgumentException("First = " + first + " must be greater than or equal to 0.")
      if (count < 0) throw new IllegalArgumentException("Count = " + count + " must be greater than or equal to 0.")
      if (first + count > accessible.size) throw new IllegalArgumentException(
        "Requested region from " + first + " to " + (first + count) +
        " exceeds the data size " + accessible.size + "."
      )
      
      shared.regions.put(first, count)
      notifyBindings()
      accessible.asInstanceOf[DataView[F, R]]
    }
    else throw new IllegalAccessException("Attributes are not writable.")
  }
  
  def src: DirectSrc = if (isAccessible) accessible else linked
}


final class AttributesSharedState private[engine] (
  val size: Int,
  val related: ReadSeq[Attributes[_ <: Format, Raw]],
  val caching: Caching.Value,
  private[engine] var persistent: InterleavedData
) extends EngineInfoRef {
  
  private[engine] def updatedRegions = regions
  private[engine] def clearDataChanges() { regions.clear() }
  private[engine] def hasDataChanges = regions.size > 0
  
  private[engine] val regions = new IntervalMap(64)
  regions.put(0, size) // Initialize as changed.
}


object Attributes {
  def apply[F <: Format, R <: Raw with Tangible]
    (size: Int, caching: Caching.Value = Caching.Dynamic)
    (implicit composition: CompositionFactory[F, _ >: R], primitives: PrimitiveFactory[F#Component, R])
  :Attributes[F, R] = {
    val data = composition.mkDataBuffer(primitives.mkDataBuffer(size*composition.components))
    fromData(data)
  }
  
  def fromData[F <: Format, R <: Raw]
    (data: ReadDataBuffer[F, _ <: R], caching: Caching.Value = Caching.Dynamic)
  :Attributes[F, R] = {
    val attributes = new Attributes[F, R](data, null)
    attributes.shared = new AttributesSharedState(
      data.size, new ReadSeq(ArrayBuffer(attributes)), caching, new InterleavedData(data)
    )
    attributes
  }
  
  def fromUncheckedSrc[F <: Format, R <: Raw]
    (src: DirectSrc with ContiguousSrc, caching: Caching.Value = Caching.Static)
    (implicit formatTag: ClassTag[F], rawTag: ClassTag[R])
  :Attributes[F, R] = {
    
    if (src.formatTag != formatTag) throw new IllegalArgumentException(
      "Data source format does not match the tag.")
      
    if (!(rawTag.runtimeClass.isAssignableFrom(RawEnum.ClassTags.fromRawEnum(src.rawEnum).runtimeClass)))
      throw new IllegalArgumentException("Data source raw type does not match the tag.")
      
    if (src.isInstanceOf[ReadDataBuffer[_, _]]) {
      fromData(src.asInstanceOf[ReadDataBuffer[F, R]], caching)
    }
    else {
      val attributes = new Attributes[F, R](null, src)
      attributes.shared = new AttributesSharedState(
        src.size, new ReadSeq(ArrayBuffer(attributes)), caching, null
      )
      attributes
    }
  }
}


// TODO restore DelayedInit when this scala bug is fixed: https://issues.scala-lang.org/browse/SI-4683
class interleaved(val caching: Caching.Value = Caching.Static) { // extends DelayedInit { 
  private val related = ArrayBuffer[Attributes[_ <: Format, Raw]]()
  
  val Attributes = new InterleavedFactory
  
  final class InterleavedFactory {
    def fromData[F <: Format, R <: Raw](view: ReadDataView[F, _ <: R])
    :Attributes[F, R] = {
      val attributes = new Attributes[F, R](view, null)
      related += attributes
      attributes
    }
    
    def fromUncheckedSrc[F <: Format, R <: Raw](src: DirectSrc)
      (implicit formatTag: ClassTag[F], rawTag: ClassTag[R])
    :Attributes[F, R] = {
      
      if (src.formatTag != formatTag) throw new IllegalArgumentException(
        "Data source format does not match the tag.")
        
      if (!(rawTag.runtimeClass.isAssignableFrom(RawEnum.ClassTags.fromRawEnum(src.rawEnum).runtimeClass)))
        throw new IllegalArgumentException("Data source raw type does not match the tag.")
    
      if (src.isInstanceOf[ReadDataView[_, _]]) {
        fromData[F, R](src.asInstanceOf[ReadDataView[F, R]])
      }
      else {
        val attributes = new Attributes[F, R](null, src)
        related += attributes
        attributes
      }
    }
  }
  
  def delayedInit(init: => Unit) {
    init
    
    if (related.size > 0) {
      val sources = related.map(_.src)
      val isAccessible = related.head.isInstanceOf[RawView]
      
      val persistent =
        if (isAccessible) new InterleavedData(sources.asInstanceOf[Seq[RawView]]: _*)
        else { InterleavedData.verifyFormat(sources); null }
      
      val sharedState = new AttributesSharedState(related.head.src.size, new ReadSeq(related), caching, persistent)
      
      var i = 0; while (i < related.length) {
        related(i).shared = sharedState
        
        i += 1
      }
    }
  }
}


/** Specialized class to keep track of update range for VBO.
 * 
 * @param initCapacity
 * @param mergeTolerance (mergeTolerance + 1) is the minimum gap between two intervals. If two consecutive intervals
 *   are closer than this gap, they will be merged.
 *   Example: intervals (1 until 5) and (10 until 20) have a gap of 5 between them, so if tolerance is set
 *   to 5 or more, these two intervals will be merged into (1 until 20) when calling '''merge()'''.
 */
final class IntervalMap(private val initCapacity: Int, val mergeTolerance: Int) {
  
  def this(mergeTolerance: Int) {
    this(4, mergeTolerance)
  }
  
  
  private[this] var array = new Array[Long](initCapacity)
  private[this] var size0 = 0
  private[this] var needsMerge = false
  private[this] var putCount = 0
  
  /* Auto merge after a predefined number of added elements to prevent memory leaks when the interval map is
   * updated by the application but never merged because the parent mesh is discarded by the frustum culling.
   */
  private[this] val autoMergeAt = 64
  
  
  def size = size0
  
  def first(index: Int) = upper(array(index))
  def count(index: Int) = lower(array(index))
  
  private def upper(n: Long) = (n >> 32).toInt
  private def lower(n: Long) = n.toInt
  
  private def put(index: Int, first: Int, count: Int) {
    array(index) = ((first.toLong) << 32) | count
  }
  
  /** Empty ranges are ignored.
   * 
   * @param start the beginning of the range (inclusive)
   * @param end the end of the range (exclusive)
   */
  def put(first: Int, count: Int) {
    if (first < 0 || count < 0) throw new IndexOutOfBoundsException(
      "First and count must be non-negative: first = " + first + ", count = " + count + "."
    )
    if (count == 0) return
    
    
    if (array.length == size0) {
      val newarray = new Array[Long](2*size0)
      System.arraycopy(array, 0, newarray, 0, size0)
      array = newarray
    }
    
    put(size0, first, count)
    size0 += 1
    
    needsMerge = true
    
    putCount += 1
    if (putCount > autoMergeAt) merge()
  }
  
  def merge() {
    if (!needsMerge || size0 < 2) {
      needsMerge = false
      return
    }
    
    java.util.Arrays.sort(array, 0, size0)
    
    var lastStart = first(0)
    var lastEnd = lastStart + count(0)
    
    var lastRange = 0
    
    val size = size0; var i = 1; while (i < size) {
      val start = first(i)
      val end = start + count(i)
      
      if (start <= lastEnd + mergeTolerance) {
        if (end > lastEnd) {
          lastEnd = end
        }
      } else {
        put(lastRange, lastStart, lastEnd - lastStart)
        lastRange += 1
        lastStart = start
        lastEnd = end
      }
      
      i += 1
    }
    
    put(lastRange, lastStart, lastEnd - lastStart)
    size0 = lastRange + 1
    
    needsMerge = false
    putCount = 0
  }
  
  def clear() {
    size0 = 0
    needsMerge = false
    putCount = 0
  }
  
  override def toString :String = {
    "IntervalMap(" +
      array.take(size0).map(n => (upper(n), lower(n))).mkString(", ") +
    ")"
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy